From 3ba2a4d0981067664f3216e94a79ae0b4c3bfecf Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:05:16 +0200 Subject: [PATCH 1/4] core/{state, vm}: Nyota contract create init simplification Co-authored-by: Tanishq Jasoria --- core/state/access_events.go | 14 +++++++++++++- core/state/access_events_test.go | 4 ++-- core/vm/evm.go | 28 ++++++++++++++++------------ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/core/state/access_events.go b/core/state/access_events.go index c593f8202721..4076b9e7b5e8 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -117,11 +117,23 @@ func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) return gas } +// ContractCreateCPreheck charges access costs before +// a contract creation is initiated. It is just reads, because the +// address collision is done before the transfer, and so no write +// are guaranteed to happen at this point. +func (aw *AccessEvents) ContractCreatePreCheckGas(addr common.Address) uint64 { + var gas uint64 + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false) + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) + return gas +} + // ContractCreateInitGas returns the access gas costs for the initialization of // a contract creation. -func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 { +func (ae *AccessEvents) ContractCreateInitGas(addr common.Address) uint64 { var gas uint64 gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true) return gas } diff --git a/core/state/access_events_test.go b/core/state/access_events_test.go index a35266950e64..e31fa76256a3 100644 --- a/core/state/access_events_test.go +++ b/core/state/access_events_test.go @@ -100,13 +100,13 @@ func TestContractCreateInitGas(t *testing.T) { } // Check cold read cost, without a value - gas := ae.ContractCreateInitGas(testAddr, false) + gas := ae.ContractCreateInitGas(testAddr) if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) } // Check warm read cost - gas = ae.ContractCreateInitGas(testAddr, false) + gas = ae.ContractCreateInitGas(testAddr) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } diff --git a/core/vm/evm.go b/core/vm/evm.go index a9c4ae9fe54d..6c8b243ebf0e 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -448,6 +448,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } evm.StateDB.SetNonce(caller.Address(), nonce+1) + // Charge the contract creation init gas in verkle mode + if evm.chainRules.IsEIP4762 { + statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address) + if statelessGas > gas { + return nil, common.Address{}, 0, ErrOutOfGas + } + } + // We add this to the access list _before_ taking a snapshot. Even if the // creation fails, the access-list change should not be rolled back. if evm.chainRules.IsEIP2929 { @@ -484,6 +492,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.chainRules.IsEIP158 { evm.StateDB.SetNonce(address, 1) } + // Charge the contract creation init gas in verkle mode + if evm.chainRules.IsEIP4762 { + statelessGas := evm.AccessEvents.ContractCreateInitGas(address) + if statelessGas > gas { + return nil, common.Address{}, 0, ErrOutOfGas + } + gas = gas - statelessGas + } evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) // Initialise a new contract and set the code that is to be used by the EVM. @@ -505,13 +521,6 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // initNewContract runs a new contract's creation code, performs checks on the // resulting code that is to be deployed, and consumes necessary gas. func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int) ([]byte, error) { - // Charge the contract creation init gas in verkle mode - if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { - return nil, ErrOutOfGas - } - } - ret, err := evm.interpreter.Run(contract, nil, false) if err != nil { return ret, err @@ -533,11 +542,6 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu return ret, ErrCodeStoreOutOfGas } } else { - // Contract creation completed, touch the missing fields in the contract - if !contract.UseGas(evm.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { - return ret, ErrCodeStoreOutOfGas - } - if len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { return ret, ErrCodeStoreOutOfGas } From 48aa710544f8d595d77afa35ae1aeb9f200d0813 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 10 Sep 2024 21:50:48 +0200 Subject: [PATCH 2/4] trace new gas change --- core/tracing/hooks.go | 2 ++ core/vm/evm.go | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index aa66dc49ff09..4ac4e0c8c518 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -306,6 +306,8 @@ const ( GasChangeWitnessContractCreation GasChangeReason = 16 // GasChangeWitnessCodeChunk is the amount charged for touching one or more contract code chunks GasChangeWitnessCodeChunk GasChangeReason = 17 + // GasChangeWitnessContractCollisionCheck the amount charged for checking a contract collision + GasChangeWitnessContractCollisionCheck GasChangeReason = 17 // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as // it will be "manually" tracked by a direct emit of the gas change event. diff --git a/core/vm/evm.go b/core/vm/evm.go index 6c8b243ebf0e..616668d565cc 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -454,6 +454,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if statelessGas > gas { return nil, common.Address{}, 0, ErrOutOfGas } + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractCollisionCheck) + } + gas = gas - statelessGas } // We add this to the access list _before_ taking a snapshot. Even if the @@ -498,6 +502,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if statelessGas > gas { return nil, common.Address{}, 0, ErrOutOfGas } + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractInit) + } gas = gas - statelessGas } evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) From de0b3f6396e893e6e2cda63838b1e29a07932595 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:35:26 +0200 Subject: [PATCH 3/4] fix failing unit test Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- core/state/access_events_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/access_events_test.go b/core/state/access_events_test.go index e31fa76256a3..10630b3181b5 100644 --- a/core/state/access_events_test.go +++ b/core/state/access_events_test.go @@ -101,7 +101,7 @@ func TestContractCreateInitGas(t *testing.T) { // Check cold read cost, without a value gas := ae.ContractCreateInitGas(testAddr) - if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { + if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + 2*params.WitnessChunkWriteCost + 2*params.WitnessChunkReadCost; gas != want { t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) } From aa139845ff8e0a1c28e4b044bd35ba7793ca9f36 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:29:02 +0200 Subject: [PATCH 4/4] fix linter --- core/state/access_events.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/state/access_events.go b/core/state/access_events.go index 4076b9e7b5e8..2b270ccafab3 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -121,10 +121,10 @@ func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) // a contract creation is initiated. It is just reads, because the // address collision is done before the transfer, and so no write // are guaranteed to happen at this point. -func (aw *AccessEvents) ContractCreatePreCheckGas(addr common.Address) uint64 { +func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address) uint64 { var gas uint64 - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false) - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) return gas }