From cd2095fb4abaea26859f6421ec674f6aeab3c96e Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 9 Apr 2026 15:04:04 +0200 Subject: [PATCH] core/vm: fix two bugs Fixes the collision bugs and create gas ordering --- core/vm/evm.go | 4 +--- core/vm/gas_table.go | 50 ++++++++++++++++++++++++++++------------- core/vm/instructions.go | 28 ++--------------------- 3 files changed, 37 insertions(+), 45 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index f052207ab9..0d4600d6d8 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -539,9 +539,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } // Burn all gas on collision - collisionUsed := GasUsed{RegularGas: gas.RegularGas} - gas.RegularGas = 0 - return nil, common.Address{}, gas, collisionUsed, ErrContractAddressCollision + return nil, common.Address{}, GasBudget{}, GasUsed{}, ErrContractAddressCollision } // Create a new account on the state only if the object was not present. // It might be possible the contract code is deployed to a pre-existent diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index c76ff4ff4c..c0c093c2f1 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -291,10 +291,19 @@ var ( gasMLoad = pureMemoryGascost gasMStore8 = pureMemoryGascost gasMStore = pureMemoryGascost - gasCreate = pureMemoryGascost ) +func gasCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } + return pureMemoryGascost(evm, contract, stack, mem, memorySize) +} + func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } gas, err := memoryGasCost(mem, memorySize) if err != nil { return GasCosts{}, err @@ -313,6 +322,9 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS } func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } gas, err := memoryGasCost(mem, memorySize) if err != nil { return GasCosts{}, err @@ -333,6 +345,9 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m } func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } gas, err := memoryGasCost(mem, memorySize) if err != nil { return GasCosts{}, err @@ -532,6 +547,9 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } gas, err := memoryGasCost(mem, memorySize) if err != nil { return GasCosts{}, err @@ -540,19 +558,20 @@ func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m if overflow { return GasCosts{}, ErrGasUintOverflow } - // Cap word gas at MaxInitCodeSizeAmsterdam to avoid overflow. - // The actual init code size check happens in create() for graceful failure. - wordSize := min(size, params.MaxInitCodeSizeAmsterdam) - words := (wordSize + 31) / 32 - // Account creation is a fixed state gas cost, not proportional to init code size. - // Code storage state gas is charged separately in initNewContract. - stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte - // CREATE uses InitCodeWordGas (EIP-3860); Keccak256WordGas is only for CREATE2. + if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { + return GasCosts{}, err + } + // Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow + words := (size + 31) / 32 wordGas := params.InitCodeWordGas * words + stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil } func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } gas, err := memoryGasCost(mem, memorySize) if err != nil { return GasCosts{}, err @@ -561,15 +580,14 @@ func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if overflow { return GasCosts{}, ErrGasUintOverflow } - // Cap word gas at MaxInitCodeSizeAmsterdam to avoid overflow. - // The actual init code size check happens in create() for graceful failure. - wordSize := min(size, params.MaxInitCodeSizeAmsterdam) - words := (wordSize + 31) / 32 - // Account creation is a fixed state gas cost, not proportional to init code size. - // Code storage state gas is charged separately in initNewContract. - stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte + if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { + return GasCosts{}, err + } + // Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow + words := (size + 31) / 32 // CREATE2 charges both InitCodeWordGas (EIP-3860) and Keccak256WordGas (for address hashing). wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words + stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 71cfe8cf46..f76ad4727b 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -648,28 +648,16 @@ func opSwap16(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { } func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { - if evm.readOnly { - return nil, ErrWriteProtection - } var ( value = scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) - gas = scope.Contract.Gas ) + gas := scope.Contract.Gas if evm.chainRules.IsEIP150 { gas.RegularGas -= gas.RegularGas / 64 } - // EIP-7954: check init code size after gas is charged (by the gas function) - // but before execution. This aborts the caller's execution, ensuring all - // regular gas is consumed while the state gas spill is tracked. - if evm.chainRules.IsAmsterdam { - if err := CheckMaxInitCodeSize(&evm.chainRules, uint64(len(input))); err != nil { - return nil, err - } - } - // reuse size int for stackvalue stackvalue := size @@ -701,29 +689,17 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { } func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { - if evm.readOnly { - return nil, ErrWriteProtection - } var ( endowment = scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop() salt = scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) - gas = scope.Contract.Gas ) // Apply EIP150 + gas := scope.Contract.Gas gas.RegularGas -= gas.RegularGas / 64 - // EIP-7954: check init code size after gas is charged (by the gas function) - // but before execution. This aborts the caller's execution, ensuring all - // regular gas is consumed while the state gas spill is tracked. - if evm.chainRules.IsAmsterdam { - if err := CheckMaxInitCodeSize(&evm.chainRules, uint64(len(input))); err != nil { - return nil, err - } - } - regularGasUsed := scope.Contract.GasUsed.RegularGas scope.Contract.UseGas(GasCosts{RegularGas: gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)