diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 219f0b80c18a..0e4d52c32581 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -343,7 +343,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, // Amount is in gwei, turn into wei amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) statedb.AddBalance(w.Address, amount) - statedb.Witness().TouchFullAccount(w.Address[:], true, nil) + statedb.Witness().TouchFullAccount(w.Address[:], true, math.MaxUint64) } if chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) { if err := overlay.OverlayVerkleTransition(statedb, common.Hash{}, chainConfig.OverlayStride); err != nil { diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 741653f5c906..b792fbf39967 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" @@ -358,7 +359,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. state.AddBalance(w.Address, amount) // The returned gas is not charged - state.Witness().TouchFullAccount(w.Address[:], true, nil) + state.Witness().TouchFullAccount(w.Address[:], true, math.MaxUint64) } if chain.Config().IsVerkle(header.Number, header.Time) { diff --git a/core/state/access_witness.go b/core/state/access_witness.go index 7fdfcec6fe50..43d98b927564 100644 --- a/core/state/access_witness.go +++ b/core/state/access_witness.go @@ -18,6 +18,7 @@ package state import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" @@ -91,69 +92,72 @@ func (aw *AccessWitness) Copy() *AccessWitness { return naw } -func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool, useGasFn UseGasFn) bool { +func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool, availableGas uint64) uint64 { + var gas uint64 for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ { - if _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite, useGasFn); !ok { - return false + consumed, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite, availableGas) + if consumed < wanted { + return wanted } + availableGas -= consumed + gas += consumed } - return true + return gas } -func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte, useGasFn UseGasFn) bool { - chargedGas, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, useGasFn) - return ok && (chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929)) +func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte, availableGas uint64) uint64 { + _, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas) + if wanted == 0 { + wanted = params.WarmStorageReadCostEIP2929 + } + return wanted } -func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte, useGasFn UseGasFn) bool { - _, ok := aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, useGasFn) - if !ok { - return false - } - _, ok = aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, useGasFn) - return ok +func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte, availableGas uint64) uint64 { + chargedGas, _ := aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas) + chargedGas, _ = aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-chargedGas) + return chargedGas } // TouchAndChargeContractCreateCheck 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 *AccessWitness) TouchAndChargeContractCreateCheck(addr []byte, useGasFn UseGasFn) bool { - if _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, useGasFn); !ok { - return false - } - _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, useGasFn) - return ok +func (aw *AccessWitness) TouchAndChargeContractCreateCheck(addr []byte, availableGas uint64) uint64 { + gas1, wanted1 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas) + _, wanted2 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-gas1) + return wanted1 + wanted2 } // TouchAndChargeContractCreateInit charges access costs to initiate // a contract creation. -func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, useGasFn UseGasFn) bool { - if _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, useGasFn); !ok { - return false - } - _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, useGasFn) - return ok +func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, availableGas uint64) (uint64, uint64) { + gas1, wanted1 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas) + gas2, wanted2 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-gas1) + return gas1 + gas2, wanted1 + wanted2 } func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) { for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ { - aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey, nil) + aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey, math.MaxUint64) } } func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) { - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, nil) - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, nil) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, math.MaxUint64) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, math.MaxUint64) } -func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool, useGasFn UseGasFn, warmCostCharging bool) bool { +func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool, availableGas uint64, warmCostCharging bool) uint64 { treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes()) - chargedGas, ok := aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, useGasFn) - return ok && (!warmCostCharging || chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929)) + _, wanted := aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas) + if wanted == 0 && warmCostCharging { + wanted = params.WarmStorageReadCostEIP2929 + } + return wanted } -func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool, useGasFn UseGasFn) (uint64, bool) { +func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) { branchKey := newBranchAccessKey(addr, treeIndex) chunkKey := newChunkAccessKey(branchKey, subIndex) @@ -196,10 +200,9 @@ func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256 gas += params.WitnessChunkFillCost } - if useGasFn != nil { - if ok := useGasFn(gas); !ok { - return 0, false - } + if availableGas < gas { + // consumed != wanted + return availableGas, gas } if branchRead { @@ -212,11 +215,11 @@ func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256 aw.chunks[chunkKey] = AccessWitnessReadFlag } if chunkWrite { - chunkWrite = true aw.chunks[chunkKey] |= AccessWitnessWriteFlag } - return gas, true + // consumed == wanted + return gas, gas } type branchAccessKey struct { @@ -244,7 +247,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { } // touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs -func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool, useGasFn UseGasFn) bool { +func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, 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 // 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 @@ -252,7 +255,7 @@ func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, s // is already in the AccessWitness so a stateless verifier can see that // the code from the last leaf is not needed. if size == 0 || startPC >= codeLen { - return true + return 0, 0 } endPC := startPC + size @@ -263,23 +266,44 @@ func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, s endPC -= 1 // endPC is the last bytecode that will be touched. } + var statelessGasCharged uint64 for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) subIndex := byte((chunkNumber + 128) % 256) - if _, ok := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, useGasFn); !ok { - return false + consumed, wanted := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas) + // did we OOG ? + if wanted > consumed { + return statelessGasCharged + consumed, statelessGasCharged + wanted + } + var overflow bool + statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed) + if overflow { + panic("overflow when adding gas") } + availableGas -= consumed } - return true + return statelessGasCharged, statelessGasCharged } -func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool, useGasFn UseGasFn, warmCostCharging bool) bool { - chargedGas, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, useGasFn) - return ok && (!warmCostCharging || chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929)) +func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool, availableGas uint64, warmCostCharging bool) uint64 { + chargedGas, _ := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas) + if chargedGas == 0 && availableGas > 0 && warmCostCharging { + if availableGas > params.WarmStorageReadCostEIP2929 { + return availableGas + } + chargedGas = params.WarmStorageReadCostEIP2929 + } + return chargedGas } -func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool, useGasFn UseGasFn) bool { - chargedGas, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, useGasFn) - return ok && (chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929)) +func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool, availableGas uint64) uint64 { + chargedGas, _ := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas) + if chargedGas == 0 && availableGas > 0 { + if availableGas > params.WarmStorageReadCostEIP2929 { + return availableGas + } + chargedGas = params.WarmStorageReadCostEIP2929 + } + return chargedGas } diff --git a/core/state_processor.go b/core/state_processor.go index aa584845355a..9ad1a1a518a8 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -23,6 +23,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/state" @@ -177,7 +178,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo func InsertBlockHashHistoryAtEip2935Fork(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash, chain consensus.ChainHeaderReader) { // Make sure that the historical contract is added to the witness - statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true, nil) + statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true, math.MaxUint64) ancestor := chain.GetHeader(prevHash, prevNumber) for i := prevNumber; i > 0 && i >= prevNumber-params.Eip2935BlockHashHistorySize; i-- { @@ -191,5 +192,5 @@ func ProcessParentBlockHash(statedb *state.StateDB, prevNumber uint64, prevHash var key common.Hash binary.BigEndian.PutUint64(key[24:], ringIndex) statedb.SetState(params.HistoryStorageAddress, key, prevHash) - statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true, nil, false) + statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true, math.MaxUint64, false) } diff --git a/core/state_transition.go b/core/state_transition.go index 45a736d5e189..72c8c68a17af 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -453,7 +453,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true, nil) + st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true, math.MaxUint64) } } diff --git a/core/vm/common.go b/core/vm/common.go index 226a337acd56..ba75950e370b 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -92,15 +92,3 @@ func allZero(b []byte) bool { } return true } - -type gasConsumer struct { - availableGas uint64 -} - -func (gc *gasConsumer) consumeGas(gas uint64) bool { - if gc.availableGas < gas { - return false - } - gc.availableGas -= gas - return true -} diff --git a/core/vm/eips.go b/core/vm/eips.go index 73fbf703c0b7..1df79f6cffd0 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -307,26 +307,26 @@ func enable6780(jt *JumpTable) { func enable4762(jt *JumpTable) { jt[SSTORE].constantGas = 0 - jt[SSTORE].dynamicGas = nil + jt[SSTORE].dynamicGas = gasSStore4762 jt[SLOAD].constantGas = 0 - jt[SLOAD].dynamicGas = nil - jt[BALANCE].dynamicGas = nil + jt[SLOAD].dynamicGas = gasSLoad4762 + jt[BALANCE].dynamicGas = gasBalance4762 jt[BALANCE].constantGas = 0 jt[EXTCODESIZE].constantGas = 0 - jt[EXTCODESIZE].dynamicGas = nil + jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762 jt[EXTCODEHASH].constantGas = 0 - jt[EXTCODEHASH].dynamicGas = nil + jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 jt[EXTCODECOPY].constantGas = 0 - jt[EXTCODECOPY].dynamicGas = gasExtCodeCopy - jt[SELFDESTRUCT].dynamicGas = nil + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762 jt[CREATE].constantGas = params.CreateNGasEip4762 jt[CREATE2].constantGas = params.CreateNGasEip4762 jt[CALL].constantGas = 0 - jt[CALL].dynamicGas = gasCall + jt[CALL].dynamicGas = gasCallEIP4762 jt[CALLCODE].constantGas = 0 - jt[CALLCODE].dynamicGas = gasCallCode + jt[CALLCODE].dynamicGas = gasCallCodeEIP4762 jt[STATICCALL].constantGas = 0 - jt[STATICCALL].dynamicGas = gasStaticCall + jt[STATICCALL].dynamicGas = gasStaticCallEIP4762 jt[DELEGATECALL].constantGas = 0 - jt[DELEGATECALL].dynamicGas = gasDelegateCall + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP4762 } diff --git a/core/vm/evm.go b/core/vm/evm.go index ccf9ce4d4e36..11bc4eebb72c 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -202,13 +202,12 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 { // add proof of absence to witness - gc := gasConsumer{availableGas: gas} - ok := evm.Accesses.TouchFullAccount(addr.Bytes(), false, gc.consumeGas) - if !ok { + wgas := evm.Accesses.TouchFullAccount(addr.Bytes(), false, gas) + if gas < wgas { evm.StateDB.RevertToSnapshot(snapshot) return nil, 0, ErrOutOfGas } - gas = gc.availableGas + gas -= wgas } if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { @@ -458,11 +457,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - gc := gasConsumer{availableGas: gas} - if !evm.Accesses.TouchAndChargeContractCreateCheck(address.Bytes(), gc.consumeGas) { + statelessGas := evm.Accesses.TouchAndChargeContractCreateCheck(address.Bytes(), gas) + if statelessGas > gas { return nil, common.Address{}, 0, ErrOutOfGas } - gas = gc.availableGas + 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 @@ -483,11 +482,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - gc := gasConsumer{availableGas: gas} - if !evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), gc.consumeGas) { + consumed, wanted := evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), gas) + if consumed < wanted { return nil, common.Address{}, 0, ErrOutOfGas } - gas = gc.availableGas + gas -= consumed } evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) @@ -528,7 +527,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, err = ErrCodeStoreOutOfGas } } else { - if err == nil && len(ret) > 0 && !evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true, contract.UseGas) { + consumed, wanted := evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas) + contract.UseGas(consumed) // consumed <= contract.Gas, so no return value check is needed + if err == nil && len(ret) > 0 && (consumed < wanted) { err = ErrCodeStoreOutOfGas } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 27233cc0ddbe..ac56353d2cc6 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -403,6 +403,25 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return 0, ErrGasUintOverflow } + if evm.chainRules.IsEIP4762 { + // If value is transferred, it is charged before 1/64th + // is subtracted from the available gas pool. + if transfersValue { + gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:], contract.Gas-gas)) + if overflow { + return 0, ErrGasUintOverflow + } + } + } + + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 6006ab52b5aa..3194dcbd60e3 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -20,6 +20,8 @@ import ( "encoding/binary" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" @@ -261,11 +263,6 @@ func opAddress(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) - if interpreter.evm.chainRules.IsVerkle { - if !interpreter.evm.Accesses.TouchBasicData(address[:], false, scope.Contract.UseGas, true) { - return nil, ErrExecutionReverted - } - } slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) return nil, nil } @@ -350,17 +347,6 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) slot := scope.Stack.peek() address := slot.Bytes20() cs := uint64(interpreter.evm.StateDB.GetCodeSize(address)) - if interpreter.evm.chainRules.IsVerkle { - if _, isPrecompile := interpreter.evm.precompile(address); isPrecompile { - if !scope.Contract.UseGas(params.WarmStorageReadCostEIP2929) { - return nil, ErrExecutionReverted - } - } else { - if !interpreter.evm.Accesses.TouchBasicData(address[:], false, scope.Contract.UseGas, true) { - return nil, ErrExecutionReverted - } - } - } slot.SetUint64(cs) return nil, nil } @@ -386,8 +372,9 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ codeAddr := scope.Contract.CodeAddr paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment { - if !interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { - scope.Contract.Gas = 0 + statelessGas, wanted := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) + scope.Contract.UseGas(statelessGas) + if statelessGas < wanted { return nil, ErrOutOfGas } } @@ -403,20 +390,6 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) codeOffset = stack.pop() length = stack.pop() ) - - if interpreter.evm.chainRules.IsVerkle { - addr := common.Address(a.Bytes20()) - if _, isPrecompile := interpreter.evm.precompile(addr); isPrecompile { - if !scope.Contract.UseGas(params.WarmStorageReadCostEIP2929) { - return nil, ErrExecutionReverted - } - } else { - if !interpreter.evm.Accesses.TouchBasicData(addr[:], false, scope.Contract.UseGas, true) { - return nil, ErrExecutionReverted - } - } - } - uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { uint64CodeOffset = 0xffffffffffffffff @@ -429,8 +402,9 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - if !interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, scope.Contract.UseGas) { - scope.Contract.Gas = 0 + statelessGas, wanted := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, contract.Gas) + scope.Contract.UseGas(statelessGas) // statelessGas <= contract.Gas, so no need to check the return value + if statelessGas < wanted { return nil, ErrOutOfGas } scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) @@ -471,17 +445,6 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) - if interpreter.evm.chainRules.IsVerkle { - if _, isPrecompile := interpreter.evm.precompile(address); isPrecompile || interpreter.evm.isSystemContract(address) { - if !scope.Contract.UseGas(params.WarmStorageReadCostEIP2929) { - return nil, ErrExecutionReverted - } - } else { - if !interpreter.evm.Accesses.TouchCodeHash(address[:], false, scope.Contract.UseGas) { - return nil, ErrExecutionReverted - } - } - } if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else { @@ -496,6 +459,14 @@ func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ return nil, nil } +func getBlockHashFromContract(number uint64, statedb StateDB, witness *state.AccessWitness) (common.Hash, uint64) { + ringIndex := number % params.Eip2935BlockHashHistorySize + var pnum common.Hash + binary.BigEndian.PutUint64(pnum[24:], ringIndex) + statelessGas := witness.TouchSlotAndChargeGas(params.HistoryStorageAddress[:], pnum, false, math.MaxUint64, false) + return statedb.GetState(params.HistoryStorageAddress, pnum), statelessGas +} + func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { num := scope.Stack.peek() num64, overflow := num.Uint64WithOverflow() @@ -516,13 +487,12 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( if num64 >= lower && num64 < upper { // if Verkle is active, read it from the history contract (EIP 2935). if evm.chainRules.IsVerkle { - ringIndex := num64 % params.Eip2935BlockHashHistorySize - var pnum common.Hash - binary.BigEndian.PutUint64(pnum[24:], ringIndex) - if !evm.Accesses.TouchSlotAndChargeGas(params.HistoryStorageAddress[:], pnum, false, scope.Contract.UseGas, false) { - return nil, ErrExecutionReverted + blockHash, statelessGas := getBlockHashFromContract(num64, evm.StateDB, evm.Accesses) + if interpreter.evm.chainRules.IsEIP4762 { + if !scope.Contract.UseGas(statelessGas) { + return nil, ErrExecutionReverted + } } - blockHash := evm.StateDB.GetState(params.HistoryStorageAddress, pnum) num.SetBytes(blockHash.Bytes()) } else { num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) @@ -594,12 +564,8 @@ func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { loc := scope.Stack.peek() hash := common.Hash(loc.Bytes32()) - if interpreter.evm.chainRules.IsVerkle { - if !interpreter.evm.Accesses.TouchSlotAndChargeGas(scope.Contract.Address().Bytes(), loc.Bytes32(), false, scope.Contract.UseGas, true) { - return nil, ErrExecutionReverted - } - } val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) + loc.SetBytes(val.Bytes()) return nil, nil } @@ -610,12 +576,6 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } loc := scope.Stack.pop() val := scope.Stack.pop() - if interpreter.evm.chainRules.IsVerkle { - if !interpreter.evm.Accesses.TouchSlotAndChargeGas(scope.Contract.Address().Bytes(), loc.Bytes32(), true, scope.Contract.UseGas, true) { - return nil, ErrExecutionReverted - } - } - interpreter.evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) return nil, nil } @@ -626,7 +586,9 @@ func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } pos := scope.Stack.pop() if !scope.Contract.validJumpdest(&pos) { - if !interpreter.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(scope.Contract.CodeAddr[:], pos.Uint64(), 1, uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { + statelessGas, wanted := interpreter.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(scope.Contract.CodeAddr[:], pos.Uint64(), 1, uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) + scope.Contract.UseGas(statelessGas) + if statelessGas < wanted { return nil, ErrOutOfGas } return nil, ErrInvalidJump @@ -642,7 +604,9 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by pos, cond := scope.Stack.pop(), scope.Stack.pop() if !cond.IsZero() { if !scope.Contract.validJumpdest(&pos) { - if !interpreter.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(scope.Contract.CodeAddr[:], pos.Uint64(), 1, uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { + statelessGas, wanted := interpreter.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(scope.Contract.CodeAddr[:], pos.Uint64(), 1, uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) + scope.Contract.UseGas(statelessGas) + if statelessGas < wanted { return nil, ErrOutOfGas } return nil, ErrInvalidJump @@ -761,49 +725,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] return nil, nil } -func chargeCallVariantEIP4762(evm *EVM, scope *ScopeContext) bool { - target := common.Address(scope.Stack.Back(1).Bytes20()) - if _, isPrecompile := evm.precompile(target); isPrecompile { - return scope.Contract.UseGas(params.WarmStorageReadCostEIP2929) - } - // The charging for the value transfer is done BEFORE subtracting - // the 1/64th gas, as this is considered part of the CALL instruction. - // (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. - if !evm.Accesses.TouchAndChargeMessageCall(target.Bytes(), scope.Contract.UseGas) { - return false - } - return true - -} - func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - if interpreter.evm.chainRules.IsEIP4762 { - address := common.Address(scope.Stack.Back(1).Bytes20()) - transfersValue := !scope.Stack.Back(2).IsZero() - - // If value is transferred, it is charged before 1/64th - // is subtracted from the available gas pool. - if transfersValue { - if !interpreter.evm.Accesses.TouchAndChargeValueTransfer(scope.Contract.Address().Bytes()[:], address.Bytes()[:], scope.Contract.UseGas) { - return nil, ErrExecutionReverted - } - } - } - - var err error - interpreter.evm.callGasTemp, err = callGas(interpreter.evm.chainRules.IsEIP150, scope.Contract.Gas, 0, scope.Stack.Back(0)) - if err != nil { - return nil, err - } - if !scope.Contract.UseGas(interpreter.evm.callGasTemp) { - return nil, ErrOutOfGas - } - if !chargeCallVariantEIP4762(interpreter.evm, scope) { - return nil, ErrExecutionReverted - } - stack := scope.Stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. // We can use this as a temporary value @@ -845,9 +767,6 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - if !chargeCallVariantEIP4762(interpreter.evm, scope) { - return nil, ErrExecutionReverted - } // Pop gas. The actual gas is in interpreter.evm.callGasTemp. stack := scope.Stack // We use it as a temporary value @@ -883,9 +802,6 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - if !chargeCallVariantEIP4762(interpreter.evm, scope) { - return nil, ErrExecutionReverted - } stack := scope.Stack // Pop gas. The actual gas is in interpreter.evm.callGasTemp. // We use it as a temporary value @@ -914,9 +830,6 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - if !chargeCallVariantEIP4762(interpreter.evm, scope) { - return nil, ErrExecutionReverted - } // Pop gas. The actual gas is in interpreter.evm.callGasTemp. stack := scope.Stack // We use it as a temporary value @@ -983,41 +896,6 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - if interpreter.evm.chainRules.IsVerkle { - beneficiaryAddr := common.Address(scope.Stack.peek().Bytes20()) - contractAddr := scope.Contract.Address() - - if !interpreter.evm.Accesses.TouchBasicData(contractAddr[:], false, scope.Contract.UseGas, false) { - return nil, ErrExecutionReverted - } - balanceIsZero := interpreter.evm.StateDB.GetBalance(contractAddr).Sign() == 0 - - if _, isPrecompile := interpreter.evm.precompile(beneficiaryAddr); !(isPrecompile && balanceIsZero) { - if contractAddr != beneficiaryAddr { - if !interpreter.evm.Accesses.TouchBasicData(beneficiaryAddr[:], false, scope.Contract.UseGas, false) { - return nil, ErrExecutionReverted - } - } - // Charge write costs if it transfers value - if !balanceIsZero { - if !interpreter.evm.Accesses.TouchBasicData(contractAddr[:], true, scope.Contract.UseGas, false) { - return nil, ErrExecutionReverted - } - if contractAddr != beneficiaryAddr { - if interpreter.evm.StateDB.Exist(beneficiaryAddr) { - if !interpreter.evm.Accesses.TouchBasicData(beneficiaryAddr[:], true, scope.Contract.UseGas, false) { - return nil, ErrExecutionReverted - } - } else { - if !interpreter.evm.Accesses.TouchFullAccount(beneficiaryAddr[:], true, scope.Contract.UseGas) { - return nil, ErrExecutionReverted - } - } - } - } - } - } - if interpreter.readOnly { return nil, ErrWriteProtection } @@ -1077,8 +955,9 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // touch next chunk if PUSH1 is at the boundary. if so, *pc has // advanced past this boundary. codeAddr := scope.Contract.CodeAddr - if !interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { - scope.Contract.Gas = 0 + statelessGas, wanted := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) + scope.Contract.UseGas(statelessGas) + if statelessGas < wanted { return nil, ErrOutOfGas } } @@ -1105,8 +984,9 @@ func makePush(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsVerkle { codeAddr := scope.Contract.CodeAddr - if !interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { - scope.Contract.Gas = 0 + statelessGas, wanted := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas) + scope.Contract.UseGas(statelessGas) + if statelessGas < wanted { return nil, ErrOutOfGas } } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index c9a53584b19e..0a29133a9984 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -183,7 +183,9 @@ 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. codeAddr := contract.CodeAddr - if !in.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], pc, 1, uint64(len(contract.Code)), false, contract.UseGas) { + consumed, wanted := in.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], pc, 1, uint64(len(contract.Code)), false, contract.Gas) + contract.UseGas(consumed) + if consumed < wanted { return nil, ErrOutOfGas } } diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go new file mode 100644 index 000000000000..b08dacfcdb64 --- /dev/null +++ b/core/vm/operations_verkle.go @@ -0,0 +1,138 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" +) + +func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return evm.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true, contract.Gas, true), nil +} + +func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return evm.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false, contract.Gas, true), nil +} + +func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + return evm.Accesses.TouchBasicData(address[:], false, contract.Gas, true), nil +} + +func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + if _, isPrecompile := evm.precompile(address); isPrecompile { + return params.WarmStorageReadCostEIP2929, nil + } + return evm.Accesses.TouchBasicData(address[:], false, contract.Gas, true), nil +} + +func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + if _, isPrecompile := evm.precompile(address); isPrecompile || evm.isSystemContract(address) { + return params.WarmStorageReadCostEIP2929, nil + } + return evm.Accesses.TouchCodeHash(address[:], false, contract.Gas), nil +} + +func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + target := common.Address(stack.Back(1).Bytes20()) + if _, isPrecompile := evm.precompile(target); isPrecompile { + var overflow bool + if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } + // The charging for the value transfer is done BEFORE subtracting + // the 1/64th gas, as this is considered part of the CALL instruction. + // (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. + wgas := evm.Accesses.TouchAndChargeMessageCall(target.Bytes(), contract.Gas-gas) + return wgas + gas, nil + } +} + +var ( + gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall) + gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode) + gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall) + gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall) +) + +func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + beneficiaryAddr := common.Address(stack.peek().Bytes20()) + contractAddr := contract.Address() + + statelessGas := evm.Accesses.TouchBasicData(contractAddr[:], false, contract.Gas, false) + balanceIsZero := evm.StateDB.GetBalance(contractAddr).Sign() == 0 + + if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile && balanceIsZero { + return statelessGas, nil + } + + if contractAddr != beneficiaryAddr { + statelessGas += evm.Accesses.TouchBasicData(beneficiaryAddr[:], false, contract.Gas-statelessGas, false) + } + // Charge write costs if it transfers value + if !balanceIsZero { + statelessGas += evm.Accesses.TouchBasicData(contractAddr[:], true, contract.Gas-statelessGas, false) + if contractAddr != beneficiaryAddr { + if evm.StateDB.Exist(beneficiaryAddr) { + statelessGas += evm.Accesses.TouchBasicData(beneficiaryAddr[:], true, contract.Gas-statelessGas, false) + } else { + wanted := evm.Accesses.TouchFullAccount(beneficiaryAddr[:], true, contract.Gas-statelessGas) + if wanted > contract.Gas-statelessGas { + return statelessGas + wanted, nil + } + statelessGas += wanted + } + } + } + return statelessGas, nil +} + +func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // memory expansion first (dynamic part of pre-2929 implementation) + gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + addr := common.Address(stack.peek().Bytes20()) + + if _, isPrecompile := evm.precompile(addr); isPrecompile { + var overflow bool + if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } + wgas := evm.Accesses.TouchBasicData(addr[:], false, contract.Gas, true) + var overflow bool + if gas, overflow = math.SafeAdd(gas, wgas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +}