diff --git a/core/state/access_events.go b/core/state/access_events.go index 43c42a29de3..52dec9605e8 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -91,67 +91,104 @@ func (ae *AccessEvents) Copy() *AccessEvents { return cpy } -// AddAccount returns the gas to be charged for each of the currently cold -// member fields of an account. -func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 { - var gas uint64 // accumulate the consumed gas - consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas) - if consumed < expected { - return expected - } - gas += consumed - consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-consumed) - if consumed < expected { - return expected + gas - } - gas += expected - return gas +// AddAccount returns the gas cost for each currently cold member field of +// an account. If the available gas is insufficient to cover the total cost, +// a false flag is returned. +func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) (uint64, bool) { + var gas uint64 + cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas) + if !sufficient { + return 0, false + } + gas += cost + + cost, sufficient = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-cost) + if !sufficient { + return 0, false + } + gas += cost + return gas, true } // MessageCallGas returns the gas to be charged for each of the currently -// cold member fields of an account, that need to be touched when making a message -// call to that account. -func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) uint64 { - _, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas) - if expected == 0 { - expected = params.WarmStorageReadCostEIP2929 - } - return expected +// cold member fields of an account, that need to be touched when making +// a message call to that account. If the available gas is insufficient to +// cover the total cost, a false flag is returned. +func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) (uint64, bool) { + cost, sufficient := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas) + if !sufficient { + return 0, false + } + if cost == 0 { + if availableGas < params.WarmStorageReadCostEIP2929 { + return 0, false + } + cost = params.WarmStorageReadCostEIP2929 + } + return cost, true } // ValueTransferGas returns the gas to be charged for each of the currently -// cold balance member fields of the caller and the callee accounts. -func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) uint64 { - _, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas) - if expected1 > availableGas { - return expected1 +// cold balance member fields of the caller and the callee accounts. If the +// available gas is insufficient to cover the total cost, a false flag is +// returned. +func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) (uint64, bool) { + var gas uint64 + cost, sufficient := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas) + if !sufficient { + return 0, false } - _, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-expected1) - if expected1+expected2 > availableGas { - return params.WarmStorageReadCostEIP2929 + gas += cost + + cost, sufficient = ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-cost) + if !sufficient { + return 0, false } - return expected1 + expected2 + gas += cost + + return gas, true } -// ContractCreatePreCheckGas 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, availableGas uint64) uint64 { - consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas) - _, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-consumed) - return expected1 + expected2 +// ContractCreatePreCheckGas 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. +// If the available gas is insufficient to cover the total cost, a false flag is +// returned. +func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) (uint64, bool) { + var gas uint64 + cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas) + if !sufficient { + return 0, false + } + gas += cost + + cost, sufficient = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-cost) + if !sufficient { + return 0, false + } + gas += cost + + return gas, false } // ContractCreateInitGas returns the access gas costs for the initialization of -// a contract creation. -func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, uint64) { +// a contract creation. If the available gas is insufficient to cover the total +// cost, a false flag is returned. +func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, bool) { var gas uint64 - consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas) - gas += consumed - consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-consumed) - gas += consumed - return gas, expected1 + expected2 + cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas) + if !sufficient { + return 0, false + } + gas += cost + + cost, sufficient = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-cost) + if !sufficient { + return 0, false + } + gas += cost + + return gas, true } // AddTxOrigin adds the member fields of the sender account to the access event list, @@ -169,18 +206,28 @@ func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesnt } // SlotGas returns the amount of gas to be charged for a cold storage access. -func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 { +// If the available gas is insufficient to cover the total cost, a false flag +// is returned. +func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) (uint64, bool) { treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) - _, expected := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas) - if expected == 0 && chargeWarmCosts { - expected = params.WarmStorageReadCostEIP2929 + cost, sufficient := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas) + if !sufficient { + return 0, false + } + if cost == 0 && chargeWarmCosts { + if availableGas < params.WarmStorageReadCostEIP2929 { + return 0, false + } + cost = params.WarmStorageReadCostEIP2929 } - return expected + return cost, true } -// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the -// consumed and required gas. -func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) { +// touchAddressAndChargeGas adds a missing access event to the access list, if needed, +// and returns the cold access cost to be charged. Additionally, it returns a flag +// indicating whether there is enough available gas to cover the cost. If not, +// the event will not be included. +func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, bool) { branchKey := newBranchAccessKey(addr, treeIndex) chunkKey := newChunkAccessKey(branchKey, subIndex) @@ -224,8 +271,7 @@ func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex } if availableGas < gas { - // consumed != expected - return availableGas, gas + return 0, false } if branchRead { @@ -241,8 +287,8 @@ func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex ae.chunks[chunkKey] |= AccessWitnessWriteFlag } - // consumed == expected - return gas, gas + // consumed == wanted + return gas, true } type branchAccessKey struct { @@ -269,16 +315,18 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { return lk } -// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs -func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, uint64) { - // note that in the case where the copied code is outside the range of the +// CodeChunksRangeGas is a helper function to touch every chunk in a code range +// and charge witness gas costs. If the available gas is insufficient to cover +// the total cost, a false flag is returned. +func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, bool) { + // Note that in the case where the copied code is outside the range of the // contract code but touches the last leaf with contract code in it, - // we don't include the last leaf of code in the AccessWitness. The + // we don't include the last leaf of code in the AccessWitness. The // reason that we do not need the last leaf is the account's code size // is already in the AccessWitness so a stateless verifier can see that // the code from the last leaf is not needed. if (codeLen == 0 && size == 0) || startPC > codeLen { - return 0, 0 + return 0, true } endPC := startPC + size @@ -293,34 +341,38 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) subIndex := byte((chunkNumber + 128) % 256) - consumed, expected := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas) + cost, sufficient := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas) // did we OOG ? - if expected > consumed { - return statelessGasCharged + consumed, statelessGasCharged + expected + if !sufficient { + return 0, false } var overflow bool - statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed) + statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, cost) if overflow { + //return 0, false panic("overflow when adding gas") } - availableGas -= consumed + availableGas -= cost } - return statelessGasCharged, statelessGasCharged + return statelessGasCharged, true } // BasicDataGas adds the account's basic data to the accessed data, and returns the // amount of gas that it costs. // Note that an access in write mode implies an access in read mode, whereas an // access in read mode does not imply an access in write mode. -func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 { - _, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas) - if expected == 0 && chargeWarmCosts { +func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) (uint64, bool) { + cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas) + if !sufficient { + return 0, false + } + if cost == 0 && chargeWarmCosts { if availableGas < params.WarmStorageReadCostEIP2929 { - return availableGas + return 0, false } - expected = params.WarmStorageReadCostEIP2929 + cost = params.WarmStorageReadCostEIP2929 } - return expected + return cost, true } // CodeHashGas adds the account's code hash to the accessed data, and returns the @@ -328,13 +380,16 @@ func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availabl // in write mode. If false, the charged gas corresponds to an access in read mode. // Note that an access in write mode implies an access in read mode, whereas an access in // read mode does not imply an access in write mode. -func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 { - _, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas) - if expected == 0 && chargeWarmCosts { +func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) (uint64, bool) { + cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas) + if !sufficient { + return 0, false + } + if cost == 0 && chargeWarmCosts { if availableGas < params.WarmStorageReadCostEIP2929 { - return availableGas + return 0, false } - expected = params.WarmStorageReadCostEIP2929 + cost = params.WarmStorageReadCostEIP2929 } - return expected + return cost, true } diff --git a/core/state/access_events_test.go b/core/state/access_events_test.go index 5e1fee767c5..bbc7a33f51d 100644 --- a/core/state/access_events_test.go +++ b/core/state/access_events_test.go @@ -41,50 +41,50 @@ func TestAccountHeaderGas(t *testing.T) { ae := NewAccessEvents(utils.NewPointCache(1024)) // Check cold read cost - gas := ae.BasicDataGas(testAddr, false, math.MaxUint64, false) + gas, _ := ae.BasicDataGas(testAddr, false, math.MaxUint64, false) if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) } // Check warm read cost - gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false) + gas, _ = ae.BasicDataGas(testAddr, false, math.MaxUint64, false) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } // Check cold read costs in the same group no longer incur the branch read cost - gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false) + gas, _ = ae.CodeHashGas(testAddr, false, math.MaxUint64, false) if gas != params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) } // Check cold write cost - gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false) + gas, _ = ae.BasicDataGas(testAddr, true, math.MaxUint64, false) if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want { t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) } // Check warm write cost - gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false) + gas, _ = ae.BasicDataGas(testAddr, true, math.MaxUint64, false) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } // Check a write without a read charges both read and write costs - gas = ae.BasicDataGas(testAddr2, true, math.MaxUint64, false) + gas, _ = ae.BasicDataGas(testAddr2, true, math.MaxUint64, false) if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) } // Check that a write followed by a read charges nothing - gas = ae.BasicDataGas(testAddr2, false, math.MaxUint64, false) + gas, _ = ae.BasicDataGas(testAddr2, false, math.MaxUint64, false) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } // Check that reading a slot from the account header only charges the // chunk read cost. - gas = ae.SlotGas(testAddr, common.Hash{}, false, math.MaxUint64, false) + gas, _ = ae.SlotGas(testAddr, common.Hash{}, false, math.MaxUint64, false) if gas != params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) } @@ -119,23 +119,23 @@ func TestMessageCallGas(t *testing.T) { ae := NewAccessEvents(utils.NewPointCache(1024)) // Check cold read cost, without a value - gas := ae.MessageCallGas(testAddr, math.MaxUint64) + gas, _ := ae.MessageCallGas(testAddr, math.MaxUint64) if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) } // Check that reading the basic data and code hash of the same account does not incur the branch read cost - gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false) + gas, _ = ae.BasicDataGas(testAddr, false, math.MaxUint64, false) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } - gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false) + gas, _ = ae.CodeHashGas(testAddr, false, math.MaxUint64, false) if gas != params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } // Check warm read cost - gas = ae.MessageCallGas(testAddr, math.MaxUint64) + gas, _ = ae.MessageCallGas(testAddr, math.MaxUint64) if gas != params.WarmStorageReadCostEIP2929 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WarmStorageReadCostEIP2929) } diff --git a/core/vm/eips.go b/core/vm/eips.go index d95fa512843..00def7511df 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -338,12 +338,7 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC } addr := common.Address(a.Bytes20()) code := interpreter.evm.StateDB.GetCode(addr) - paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas) - scope.Contract.UseGas(consumed, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) - if consumed < wanted { - return nil, ErrOutOfGas - } + paddedCodeCopy, _, _ := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) return nil, nil @@ -361,15 +356,18 @@ func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext if *pc < codeLen { scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + // The PC is not exposed in the gas func. + // TODO(rjl493456442) we either need to refactor the gas func definition + // a bit, or keep the hack here... if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall && *pc%31 == 0 { // touch next chunk if PUSH1 is at the boundary. if so, *pc has // advanced past this boundary. contractAddr := scope.Contract.Address() - consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) - scope.Contract.UseGas(wanted, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) - if consumed < wanted { + cost, sufficient := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) + if !sufficient { return nil, ErrOutOfGas } + scope.Contract.UseGas(cost, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) } } else { scope.Stack.push(integer.Clear()) @@ -393,11 +391,11 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall { contractAddr := scope.Contract.Address() - consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) - scope.Contract.UseGas(consumed, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) - if consumed < wanted { + cost, sufficient := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) + if !sufficient { return nil, ErrOutOfGas } + scope.Contract.UseGas(cost, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) } *pc += size diff --git a/core/vm/evm.go b/core/vm/evm.go index de733b19025..02b5d5c9793 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -213,8 +213,8 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g // list in write mode. If there is enough gas paying for the addition of the code // hash leaf to the access list, then account creation will proceed unimpaired. // Thus, only pay for the creation of the code hash leaf here. - wgas := evm.AccessEvents.CodeHashGas(addr, true, gas, false) - if gas < wgas { + wgas, sufficient := evm.AccessEvents.CodeHashGas(addr, true, gas, false) + if !sufficient { evm.StateDB.RevertToSnapshot(snapshot) return nil, 0, ErrOutOfGas } @@ -439,8 +439,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas) - if statelessGas > gas { + statelessGas, sufficient := evm.AccessEvents.ContractCreatePreCheckGas(address, gas) + if !sufficient { return nil, common.Address{}, 0, ErrOutOfGas } if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { @@ -487,14 +487,14 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui } // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas) - if consumed < wanted { + cost, sufficient := evm.AccessEvents.ContractCreateInitGas(address, gas) + if !sufficient { return nil, common.Address{}, 0, ErrOutOfGas } if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { - evm.Config.Tracer.OnGasChange(gas, gas-consumed, tracing.GasChangeWitnessContractInit) + evm.Config.Tracer.OnGasChange(gas, gas-cost, tracing.GasChangeWitnessContractInit) } - gas = gas - consumed + gas = gas - cost } evm.Context.Transfer(evm.StateDB, caller, address, value) @@ -541,13 +541,12 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b return ret, ErrCodeStoreOutOfGas } } else { - consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas) - contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) - if len(ret) > 0 && (consumed < wanted) { - return ret, ErrCodeStoreOutOfGas + cost, sufficient := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas) + if !sufficient { + return nil, ErrCodeStoreOutOfGas } + contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) } - evm.StateDB.SetCode(address, ret) return ret, nil } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 8f55a05e4ee..5315bed3c58 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -237,11 +237,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() - consumed, wanted := in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas) - contract.UseGas(consumed, in.evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) - if consumed < wanted { + cost, sufficient := in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas) + if !sufficient { return nil, ErrOutOfGas } + contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) } // Get the operation from the jump table and validate the stack to ensure there are diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 30f99577754..bda436aaa2c 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -25,16 +25,28 @@ import ( ) func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true, contract.Gas, true), nil + cost, sufficient := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true, contract.Gas, true) + if !sufficient { + return 0, ErrOutOfGas + } + return cost, nil } func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false, contract.Gas, true), nil + cost, sufficient := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false, contract.Gas, true) + if !sufficient { + return 0, ErrOutOfGas + } + return cost, nil } func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil + cost, sufficient := evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true) + if !sufficient { + return 0, ErrOutOfGas + } + return cost, nil } func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { @@ -42,7 +54,11 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil + cost, sufficient := evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true) + if !sufficient { + return 0, ErrOutOfGas + } + return cost, nil } func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { @@ -50,7 +66,11 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - return evm.AccessEvents.CodeHashGas(address, false, contract.Gas, true), nil + cost, sufficient := evm.AccessEvents.CodeHashGas(address, false, contract.Gas, true) + if !sufficient { + return 0, ErrOutOfGas + } + return cost, nil } func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) gasFunc { @@ -65,11 +85,11 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) ga // If value is transferred, it is charged before 1/64th // is subtracted from the available gas pool. if withTransferCosts && !stack.Back(2).IsZero() { - wantedValueTransferWitnessGas := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas) - if wantedValueTransferWitnessGas > contract.Gas { - return wantedValueTransferWitnessGas, nil + cost, sufficient := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas) + if !sufficient { + return 0, ErrOutOfGas } - witnessGas = wantedValueTransferWitnessGas + witnessGas = cost } else if isPrecompile || isSystemContract { witnessGas = params.WarmStorageReadCostEIP2929 } else { @@ -78,14 +98,14 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) ga // (so before we get to this point) // But the message call is part of the subcall, for which only 63/64th // of the gas should be available. - wantedMessageCallWitnessGas := evm.AccessEvents.MessageCallGas(target, contract.Gas-witnessGas) + cost, sufficient := evm.AccessEvents.MessageCallGas(target, contract.Gas-witnessGas) + if !sufficient { + return 0, ErrOutOfGas + } var overflow bool - if witnessGas, overflow = math.SafeAdd(witnessGas, wantedMessageCallWitnessGas); overflow { + if witnessGas, overflow = math.SafeAdd(witnessGas, cost); overflow { return 0, ErrGasUintOverflow } - if witnessGas > contract.Gas { - return witnessGas, nil - } } contract.Gas -= witnessGas @@ -116,11 +136,12 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem return 0, nil } contractAddr := contract.Address() - wanted := evm.AccessEvents.BasicDataGas(contractAddr, false, contract.Gas, false) - if wanted > contract.Gas { - return wanted, nil + cost, sufficient := evm.AccessEvents.BasicDataGas(contractAddr, false, contract.Gas, false) + if !sufficient { + return 0, ErrOutOfGas } - statelessGas := wanted + statelessGas := cost + balanceIsZero := evm.StateDB.GetBalance(contractAddr).Sign() == 0 _, isPrecompile := evm.precompile(beneficiaryAddr) isSystemContract := beneficiaryAddr == params.HistoryStorageAddress @@ -130,30 +151,30 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem } if contractAddr != beneficiaryAddr { - wanted := evm.AccessEvents.BasicDataGas(beneficiaryAddr, false, contract.Gas-statelessGas, false) - if wanted > contract.Gas-statelessGas { - return statelessGas + wanted, nil + cost, sufficient := evm.AccessEvents.BasicDataGas(beneficiaryAddr, false, contract.Gas-statelessGas, false) + if !sufficient { + return 0, ErrOutOfGas } - statelessGas += wanted + statelessGas += cost } // Charge write costs if it transfers value if !balanceIsZero { - wanted := evm.AccessEvents.BasicDataGas(contractAddr, true, contract.Gas-statelessGas, false) - if wanted > contract.Gas-statelessGas { - return statelessGas + wanted, nil + cost, sufficient := evm.AccessEvents.BasicDataGas(contractAddr, true, contract.Gas-statelessGas, false) + if !sufficient { + return 0, ErrOutOfGas } - statelessGas += wanted + statelessGas += cost if contractAddr != beneficiaryAddr { if evm.StateDB.Exist(beneficiaryAddr) { - wanted = evm.AccessEvents.BasicDataGas(beneficiaryAddr, true, contract.Gas-statelessGas, false) + cost, sufficient = evm.AccessEvents.BasicDataGas(beneficiaryAddr, true, contract.Gas-statelessGas, false) } else { - wanted = evm.AccessEvents.AddAccount(beneficiaryAddr, true, contract.Gas-statelessGas) + cost, sufficient = evm.AccessEvents.AddAccount(beneficiaryAddr, true, contract.Gas-statelessGas) } - if wanted > contract.Gas-statelessGas { - return statelessGas + wanted, nil + if !sufficient { + return 0, ErrOutOfGas } - statelessGas += wanted + statelessGas += cost } } return statelessGas, nil @@ -175,8 +196,11 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, } _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) - _, wanted := evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, contract.Gas-gas) - gas += wanted + cost, sufficient := evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, contract.Gas-gas) + if !sufficient { + return 0, ErrOutOfGas + } + gas += cost } return gas, nil } @@ -196,9 +220,29 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo } return gas, nil } - wgas := evm.AccessEvents.BasicDataGas(addr, false, contract.Gas-gas, true) + cost, sufficient := evm.AccessEvents.BasicDataGas(addr, false, contract.Gas-gas, true) + if !sufficient { + return 0, ErrOutOfGas + } var overflow bool - if gas, overflow = math.SafeAdd(gas, wgas); overflow { + if gas, overflow = math.SafeAdd(gas, cost); overflow { + return 0, ErrGasUintOverflow + } + + // Evaluate the gas cost for accessing code chunks + codeOffset := stack.Back(2) + length := stack.Back(3) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = gomath.MaxUint64 + } + code := evm.StateDB.GetCode(addr) + _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) + cost, sufficient = evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, contract.Gas-gas) + if !sufficient { + return 0, ErrOutOfGas + } + if gas, overflow = math.SafeAdd(gas, cost); overflow { return 0, ErrGasUintOverflow } return gas, nil