diff --git a/core/state/access_events.go b/core/state/access_events.go index c593f8202721..2b270ccafab3 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 (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address) uint64 { + var gas uint64 + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false) + gas += ae.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..10630b3181b5 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) - if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { + gas := ae.ContractCreateInitGas(testAddr) + 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) } // 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/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 a9c4ae9fe54d..616668d565cc 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -448,6 +448,18 @@ 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 + } + 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 // creation fails, the access-list change should not be rolled back. if evm.chainRules.IsEIP2929 { @@ -484,6 +496,17 @@ 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 + } + 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) // Initialise a new contract and set the code that is to be used by the EVM. @@ -505,13 +528,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 +549,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 }