From e72107e3d7810948a3a570ae7723aa62b1358399 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:17:25 +0100 Subject: [PATCH 01/25] simplified gas accounting layer --- consensus/beacon/consensus.go | 7 +- consensus/ethash/consensus.go | 11 --- core/state/access_witness.go | 124 ++++++++++++++++++++++++--------- core/state/accesslist/types.go | 44 ++++++++++++ core/state/statedb.go | 2 +- core/state_processor.go | 4 +- core/state_processor_test.go | 2 +- core/state_transition.go | 12 +--- core/vm/eips.go | 18 +++++ core/vm/evm.go | 35 ++++------ core/vm/gas_table.go | 14 ---- core/vm/instructions.go | 105 ++++------------------------ core/vm/interpreter.go | 2 +- core/vm/jump_table.go | 1 + core/vm/operations_acl.go | 13 ---- core/vm/operations_verkle.go | 98 ++++++++++++++++++++++++++ params/config.go | 3 + params/protocol_params.go | 1 + 18 files changed, 293 insertions(+), 203 deletions(-) create mode 100644 core/state/accesslist/types.go create mode 100644 core/vm/operations_verkle.go diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 35a7ed2b56d0..ad8894cf4db0 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-verkle" - "github.com/holiman/uint256" ) // Proof-of-stake protocol constants. @@ -357,11 +356,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. state.AddBalance(w.Address, amount) // The returned gas is not charged - state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.VersionLeafKey) - state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.BalanceLeafKey) - state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.NonceLeafKey) - state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.CodeKeccakLeafKey) - state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.CodeSizeLeafKey) + state.Witness().TouchFullAccount(w.Address[:], true) } } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 44aec25c1216..720e49b6e0d8 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -33,8 +33,6 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/trie/utils" - "github.com/holiman/uint256" "golang.org/x/crypto/sha3" ) @@ -568,19 +566,10 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header r.Div(r, big8) // This should not happen, but it's useful for replay tests - if config.IsPrague(header.Number, header.Time) { - state.Witness().TouchAddressOnReadAndComputeGas(uncle.Coinbase.Bytes(), uint256.Int{}, utils.BalanceLeafKey) - } state.AddBalance(uncle.Coinbase, r) r.Div(blockReward, big32) reward.Add(reward, r) } - if config.IsPrague(header.Number, header.Time) { - state.Witness().TouchAddressOnReadAndComputeGas(header.Coinbase.Bytes(), uint256.Int{}, utils.BalanceLeafKey) - state.Witness().TouchAddressOnReadAndComputeGas(header.Coinbase.Bytes(), uint256.Int{}, utils.VersionLeafKey) - state.Witness().TouchAddressOnReadAndComputeGas(header.Coinbase.Bytes(), uint256.Int{}, utils.NonceLeafKey) - state.Witness().TouchAddressOnReadAndComputeGas(header.Coinbase.Bytes(), uint256.Int{}, utils.CodeKeccakLeafKey) - } state.AddBalance(header.Coinbase, reward) } diff --git a/core/state/access_witness.go b/core/state/access_witness.go index 696da15fd1ef..590ef2558dbc 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" @@ -88,27 +89,33 @@ func (aw *AccessWitness) Copy() *AccessWitness { return naw } +func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { + var gas uint64 + for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite) + } + return gas +} + func (aw *AccessWitness) TouchAndChargeProofOfAbsence(addr []byte) uint64 { var gas uint64 - gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.VersionLeafKey) - gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.BalanceLeafKey) - gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey) - gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey) - gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.NonceLeafKey) + for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), false) + } return gas } func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 { var gas uint64 - gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.VersionLeafKey) - gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey) + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false) return gas } func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 { var gas uint64 - gas += aw.TouchAddressOnWriteAndComputeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey) - gas += aw.TouchAddressOnWriteAndComputeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey) + gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) return gas } @@ -116,10 +123,10 @@ func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []by // a contract creation func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 { var gas uint64 - gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.VersionLeafKey) - gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.NonceLeafKey) + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) if createSendsValue { - gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.BalanceLeafKey) + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true) } return gas } @@ -129,20 +136,16 @@ func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSen // the tree func (aw *AccessWitness) TouchAndChargeContractCreateCompleted(addr []byte) uint64 { var gas uint64 - gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.VersionLeafKey) - gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.BalanceLeafKey) - gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey) - gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey) - gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.NonceLeafKey) + for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), true) + } return gas } func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 { - aw.TouchAddressOnReadAndComputeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey) - aw.TouchAddressOnReadAndComputeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey) - aw.TouchAddressOnReadAndComputeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey) - aw.TouchAddressOnWriteAndComputeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey) - aw.TouchAddressOnWriteAndComputeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey) + for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { + aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) + } // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling. @@ -152,14 +155,14 @@ func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 { } func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 { - aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey) - aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey) - aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.CodeKeccakLeafKey) - aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, false) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, false) if sendsValue { - aw.TouchAddressOnWriteAndComputeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) } else { - aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, false) } // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address @@ -169,12 +172,14 @@ func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsVa return 0 } -func (aw *AccessWitness) TouchAddressOnWriteAndComputeGas(addr []byte, treeIndex uint256.Int, subIndex byte) uint64 { - return aw.touchAddressAndChargeGas(addr, treeIndex, subIndex, true) +func (aw *AccessWitness) TouchAddressOnWriteAndComputeGas(addr []byte, slot common.Hash) uint64 { + treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes()) + return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, true) } -func (aw *AccessWitness) TouchAddressOnReadAndComputeGas(addr []byte, treeIndex uint256.Int, subIndex byte) uint64 { - return aw.touchAddressAndChargeGas(addr, treeIndex, subIndex, false) +func (aw *AccessWitness) TouchAddressOnReadAndComputeGas(addr []byte, slot common.Hash) uint64 { + treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes()) + return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, false) } func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { @@ -259,3 +264,58 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { lk.leafKey = leafKey return lk } + +// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs +func (aw *AccessWitness) TouchCodeChunksRangeOnReadAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen 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 + // 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 + } + + endPC := startPC + size + if endPC > codeLen { + endPC = codeLen + } + if endPC > 0 { + 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) + gas := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, false) + var overflow bool + statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) + if overflow { + panic("overflow when adding gas") + } + } + + return statelessGasCharged +} + +func (aw *AccessWitness) TouchVersion(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) +} + +func (aw *AccessWitness) TouchBalance(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) +} + +func (aw *AccessWitness) TouchNonce(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) +} + +func (aw *AccessWitness) TouchCodeSize(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) +} + +func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) +} diff --git a/core/state/accesslist/types.go b/core/state/accesslist/types.go new file mode 100644 index 000000000000..53d5e03198b7 --- /dev/null +++ b/core/state/accesslist/types.go @@ -0,0 +1,44 @@ +// Copyright 2020 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 accesslist + +// AccessType specifies if a state access is for reading or writing. This +// is necessary because verkle charges different costs depending on the +// type of access. +type AccessType bool + +var ( + AccessListRead = AccessType(false) + AccessListWrite = AccessType(true) +) + +// ItemType is used in verkle mode to specify what item of an account +// is being accessed. This is necessary because verkle charges gas each +// time a new account item is accessed. +type ItemType uint64 + +const ( + Version = ItemType(1 << iota) + Balance + Nonce + CodeHash + CodeSize + LastHeaderItem +) + +const ALAllItems = Version | Balance | Nonce | CodeSize | CodeHash +const ALNoItems = ItemType(0) diff --git a/core/state/statedb.go b/core/state/statedb.go index 1b47746458b5..181dbfdd23d8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1372,7 +1372,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // - Add coinbase to access list (EIP-3651) // - Reset transient storage (EIP-1153) func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { - if rules.IsBerlin { + if rules.IsEIP2929 { // Clear out any leftover from previous executions al := newAccessList() s.accessList = al diff --git a/core/state_processor.go b/core/state_processor.go index 6961a873602e..a30cfab60285 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -35,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/trie/utils" tutils "github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-verkle" "github.com/holiman/uint256" @@ -382,6 +381,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) - index, suffix := utils.GetTreeKeyStorageSlotTreeIndexes(key[:]) - statedb.Witness().TouchAddressOnWriteAndComputeGas(params.HistoryStorageAddress[:], *index, suffix) + statedb.Witness().TouchAddressOnWriteAndComputeGas(params.HistoryStorageAddress[:], key) } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index a5fd70aada65..b72147d70a98 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -486,7 +486,7 @@ func TestProcessVerkle(t *testing.T) { txCost1 := params.TxGas txCost2 := params.TxGas contractCreationCost := intrinsicContractCreationGas + uint64(5600+700+700+700 /* creation with value */ +2739 /* execution costs */) - codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +302044 /* execution costs */) + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +198644 /* execution costs */) blockGasUsagesExpected := []uint64{ txCost1*2 + txCost2, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, diff --git a/core/state_transition.go b/core/state_transition.go index 969e7a75fb9b..49b35dd81087 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -29,8 +29,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/trie/utils" - "github.com/holiman/uint256" ) // ExecutionResult includes all output after executing given evm @@ -405,7 +403,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } st.gasRemaining -= gas - if rules.IsPrague { + if rules.IsEIP4762 { targetAddr := msg.To originAddr := msg.From @@ -480,12 +478,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.state.AddBalance(st.evm.Context.Coinbase, fee) // add the coinbase to the witness iff the fee is greater than 0 - if rules.IsPrague && fee.Sign() != 0 { - st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.VersionLeafKey) - st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.BalanceLeafKey) - st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.NonceLeafKey) - st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.CodeKeccakLeafKey) - st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.CodeSizeLeafKey) + if rules.IsEIP4762 && fee.Sign() != 0 { + st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true) } } diff --git a/core/vm/eips.go b/core/vm/eips.go index 704c1ce12745..2703c8316db2 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -37,6 +37,7 @@ var activators = map[int]func(*JumpTable){ 1884: enable1884, 1344: enable1344, 1153: enable1153, + 4762: enable4762, } // EnableEIP enables the given EIP on the config. @@ -303,3 +304,20 @@ func enable6780(jt *JumpTable) { maxStack: maxStack(1, 0), } } + +func enable4762(jt *JumpTable) { + jt[SSTORE].constantGas = 0 + jt[SSTORE].dynamicGas = gasSStore4762 + jt[SLOAD].constantGas = 0 + jt[SLOAD].dynamicGas = gasSLoad4762 + jt[BALANCE].dynamicGas = gasBalance4762 + jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762 + jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762 + jt[CREATE].constantGas = params.CreateNGasEip4762 + jt[CREATE2].constantGas = params.CreateNGasEip4762 + jt[CALL].dynamicGas = gasCallEIP4762 + jt[CALLCODE].dynamicGas = gasCallCodeEIP4762 + jt[STATICCALL].dynamicGas = gasStaticCallEIP4762 + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP4762 +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 782fc6d56740..02817fbda40b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -178,20 +178,6 @@ func (evm *EVM) SetBlockContext(blockCtx BlockContext) { evm.chainRules = evm.chainConfig.Rules(num, blockCtx.Random != nil, timestamp) } -// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool -// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false -// otherwise, do the subtraction setting the result in gasPool and return true -func tryConsumeGas(gasPool *uint64, gas uint64) bool { - // XXX check this is still needed as a func - if *gasPool < gas { - *gasPool = 0 - return false - } - - *gasPool -= gas - return true -} - // Call executes the contract associated with the addr with the given input as // parameters. It also handles any necessary value transfer required and takes // the necessary steps to create accounts and reverses the state in case of an @@ -211,11 +197,18 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas var creation bool if !evm.StateDB.Exist(addr) { - if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { - if evm.chainRules.IsPrague { - // proof of absence - tryConsumeGas(&gas, evm.Accesses.TouchAndChargeProofOfAbsence(addr.Bytes())) + if !isPrecompile && evm.chainRules.IsEIP4762 { + // add proof of absence to witness + wgas := evm.Accesses.TouchAndChargeProofOfAbsence(addr.Bytes()) + if gas < wgas { + evm.StateDB.RevertToSnapshot(snapshot) + return nil, 0, ErrOutOfGas } + gas -= wgas + } + + if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { + // Calling a non existing account, don't do anything, but ping the tracer if debug { if evm.depth == 0 { @@ -463,7 +456,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.StateDB.SetNonce(caller.Address(), nonce+1) // 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.IsBerlin { + if evm.chainRules.IsEIP2929 { evm.StateDB.AddAddressToAccessList(address) } // Ensure there's no existing contract already at the designated address @@ -519,9 +512,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } - if err == nil && evm.chainRules.IsPrague { + if err == nil && evm.chainRules.IsEIP4762 { if len(ret) > 0 { - touchCodeChunksRangeOnReadAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), evm.Accesses) + evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret))) } if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateCompleted(address.Bytes()[:])) { err = ErrOutOfGas diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 50ebf8c05b64..64b6df5cb454 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -23,8 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - trieUtils "github.com/ethereum/go-ethereum/trie/utils" - "github.com/holiman/uint256" ) // memoryGasCost calculates the quadratic gas for memory expansion. It does so @@ -100,24 +98,12 @@ var ( func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { usedGas := uint64(0) - slot := stack.Back(0) - address := slot.Bytes20() - if evm.chainRules.IsPrague { - usedGas += evm.TxContext.Accesses.TouchAddressOnReadAndComputeGas(address[:], uint256.Int{}, trieUtils.CodeSizeLeafKey) - } - return usedGas, nil } func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { usedGas := uint64(0) - if evm.chainRules.IsPrague { - where := stack.Back(0) - treeIndex, subIndex := trieUtils.GetTreeKeyStorageSlotTreeIndexes(where.Bytes()) - usedGas += evm.Accesses.TouchAddressOnReadAndComputeGas(contract.Address().Bytes(), *treeIndex, subIndex) - } - return usedGas, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7073f2d7c297..9dc465ce80f9 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -20,12 +20,10 @@ 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" - "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" ) @@ -264,13 +262,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.IsPrague { - statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(address[:], uint256.Int{}, utils.BalanceLeafKey) - if !scope.Contract.UseGas(statelessGas) { - scope.Contract.Gas = 0 - return nil, ErrOutOfGas - } - } slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) return nil, nil } @@ -355,13 +346,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.IsPrague { - statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(address[:], uint256.Int{}, utils.CodeSizeLeafKey) - if !scope.Contract.UseGas(statelessGas) { - scope.Contract.Gas = 0 - return nil, ErrOutOfGas - } - } slot.SetUint64(cs) return nil, nil } @@ -387,7 +371,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ contractAddr := scope.Contract.Address() paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) if interpreter.evm.chainRules.IsPrague && !scope.Contract.IsDeployment { - statelessGas := touchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), interpreter.evm.Accesses) + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code))) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -397,41 +381,6 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ return nil, nil } -// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs -func touchCodeChunksRangeOnReadAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, accesses *state.AccessWitness) 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 - // 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 - } - - endPC := startPC + size - if endPC > codeLen { - endPC = codeLen - } - if endPC > 0 { - 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) - gas := accesses.TouchAddressOnReadAndComputeGas(contractAddr, treeIndex, subIndex) - var overflow bool - statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) - if overflow { - panic("overflow when adding gas") - } - } - - return statelessGasCharged -} - func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( stack = scope.Stack @@ -452,7 +401,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := touchCodeChunksRangeOnReadAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), interpreter.evm.Accesses) + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code))) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -495,13 +444,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.IsPrague { - statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(address[:], uint256.Int{}, utils.CodeKeccakLeafKey) - if !scope.Contract.UseGas(statelessGas) { - scope.Contract.Gas = 0 - return nil, ErrOutOfGas - } - } if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else { @@ -520,8 +462,7 @@ func getBlockHashFromContract(number uint64, statedb StateDB, witness *state.Acc ringIndex := number % params.Eip2935BlockHashHistorySize var pnum common.Hash binary.BigEndian.PutUint64(pnum[24:], ringIndex) - treeIndex, suffix := utils.GetTreeKeyStorageSlotTreeIndexes(pnum.Bytes()) - witness.TouchAddressOnReadAndComputeGas(params.HistoryStorageAddress[:], *treeIndex, suffix) + witness.TouchAddressOnReadAndComputeGas(params.HistoryStorageAddress[:], pnum) return statedb.GetState(params.HistoryStorageAddress, pnum) } @@ -687,16 +628,16 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) gas = scope.Contract.Gas ) - if interpreter.evm.chainRules.IsPrague { - contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) - statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], value.Sign() != 0) - if !tryConsumeGas(&gas, statelessGas) { - return nil, ErrExecutionReverted - } - } if interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 } + + if interpreter.evm.chainRules.IsEIP4762 { + contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) + statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], value.Sign() != 0) + scope.Contract.UseGas(statelessGas) + } + // reuse size int for stackvalue stackvalue := size @@ -741,13 +682,11 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) gas = scope.Contract.Gas ) - if interpreter.evm.chainRules.IsPrague { + if interpreter.evm.chainRules.IsEIP4762 { codeAndHash := &codeAndHash{code: input} contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], endowment.Sign() != 0) - if !tryConsumeGas(&gas, statelessGas) { - return nil, ErrExecutionReverted - } + scope.Contract.UseGas(statelessGas) } // Apply EIP150 @@ -962,22 +901,6 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) tracer.CaptureExit([]byte{}, 0, nil) } - if interpreter.evm.chainRules.IsPrague { - contractAddr := scope.Contract.Address() - beneficiaryAddr := beneficiary.Bytes20() - // If the beneficiary isn't the contract, we need to touch the beneficiary's balance. - // If the beneficiary is the contract itself, there're two possibilities: - // 1. The contract was created in the same transaction: the balance is already touched (no need to touch again) - // 2. The contract wasn't created in the same transaction: there's no net change in balance, - // and SELFDESTRUCT will perform no action on the account header. (we touch since we did SubBalance+AddBalance above) - if contractAddr != beneficiaryAddr || interpreter.evm.StateDB.WasCreatedInCurrentTx(contractAddr) { - statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(beneficiaryAddr[:], uint256.Int{}, utils.BalanceLeafKey) - if !scope.Contract.UseGas(statelessGas) { - scope.Contract.Gas = 0 - return nil, ErrOutOfGas - } - } - } return nil, errStopToken } @@ -1025,7 +948,7 @@ 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. contractAddr := scope.Contract.Address() - statelessGas := touchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), interpreter.evm.Accesses) + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code))) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -1054,7 +977,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsPrague { contractAddr := scope.Contract.Address() - statelessGas := touchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), interpreter.evm.Accesses) + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code))) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 17b30fae1203..93750de2ca3a 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -183,7 +183,7 @@ 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() - contract.Gas -= touchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), in.evm.TxContext.Accesses) + contract.Gas -= in.evm.TxContext.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], pc, 1, uint64(len(contract.Code))) } // Get the operation from the jump table and validate the stack to ensure there are diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 5dcabe387d6f..11a04a201317 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -84,6 +84,7 @@ func validate(jt JumpTable) JumpTable { func newPragueInstructionSet() JumpTable { instructionSet := newShanghaiInstructionSet() enable6780(&instructionSet) + enable4762(&instructionSet) return validate(instructionSet) } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 4d4fe8aed3e6..dde3338ec18d 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -22,7 +22,6 @@ 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" ) func makeGasSStoreFunc(clearingRefund uint64) gasFunc { @@ -52,11 +51,6 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { } value := common.Hash(y.Bytes32()) - if evm.chainRules.IsPrague { - treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(x.Bytes()) - cost += evm.Accesses.TouchAddressOnWriteAndComputeGas(contract.Address().Bytes(), *treeIndex, subIndex) - } - if current == value { // noop (1) // EIP 2200 original clause: // return params.SloadGasEIP2200, nil @@ -111,13 +105,6 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me slot := common.Hash(loc.Bytes32()) var gasUsed uint64 - if evm.chainRules.IsPrague { - where := stack.Back(0) - treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(where.Bytes()) - addr := contract.Address() - gasUsed += evm.Accesses.TouchAddressOnReadAndComputeGas(addr.Bytes(), *treeIndex, subIndex) - } - // Check slot presence in the access list if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { // If the caller cannot afford the cost, this change will be rolled back diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go new file mode 100644 index 000000000000..3c4f11121394 --- /dev/null +++ b/core/vm/operations_verkle.go @@ -0,0 +1,98 @@ +// 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/params" +) + +func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas := evm.StateDB.Witness().TouchAddressOnWriteAndComputeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32())) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas := evm.StateDB.Witness().TouchAddressOnReadAndComputeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32())) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + return evm.StateDB.Witness().TouchBalance(address[:], false), nil +} + +func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + return evm.StateDB.Witness().TouchCodeSize(address[:], false), nil +} + +func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + return evm.StateDB.Witness().TouchCodeHash(address[:], false), 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 + } + wgas, err := evm.StateDB.Witness().TouchCodeSize(contract.Address().Bytes(), false), nil + if err != nil { + return 0, err + } + gas += wgas + wgas, err = evm.StateDB.Witness().TouchCodeHash(contract.Address().Bytes(), false), nil + if err != nil { + return 0, err + } + 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() + // If the beneficiary isn't the contract, we need to touch the beneficiary's balance. + // If the beneficiary is the contract itself, there're two possibilities: + // 1. The contract was created in the same transaction: the balance is already touched (no need to touch again) + // 2. The contract wasn't created in the same transaction: there's no net change in balance, + // and SELFDESTRUCT will perform no action on the account header. (we touch since we did SubBalance+AddBalance above) + if contractAddr != beneficiaryAddr || evm.StateDB.WasCreatedInCurrentTx(contractAddr) { + statelessGas := evm.Accesses.TouchBalance(beneficiaryAddr[:], false) + if !contract.UseGas(statelessGas) { + contract.Gas = 0 + return 0, ErrOutOfGas + } + return statelessGas, nil + } + return 0, nil +} diff --git a/params/config.go b/params/config.go index 19e633a71def..94dcb57b2fe2 100644 --- a/params/config.go +++ b/params/config.go @@ -806,6 +806,7 @@ func (err *ConfigCompatError) Error() string { type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool + IsEIP2929, IsEIP4762 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsBerlin, IsLondon bool IsMerge, IsShanghai, IsCancun, IsPrague bool @@ -828,6 +829,8 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), + IsEIP2929: c.IsBerlin(num) && !c.IsPrague(num, timestamp), + IsEIP4762: c.IsPrague(num, timestamp), IsLondon: c.IsLondon(num), IsMerge: isMerge, IsShanghai: c.IsShanghai(num, timestamp), diff --git a/params/protocol_params.go b/params/protocol_params.go index 8aad52103979..e64836b31417 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -86,6 +86,7 @@ const ( LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. Create2Gas uint64 = 32000 // Once per CREATE2 operation + CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. From 95da72b6621638dac0c52c8177709d61b8f6fa83 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sat, 16 Mar 2024 20:31:23 +0100 Subject: [PATCH 02/25] integrate some review feedback --- core/state/access_witness.go | 32 +++-------------------- core/state/accesslist/types.go | 44 -------------------------------- core/state_processor.go | 4 +-- core/state_processor_test.go | 46 +++++++++++++++++++++++++++++++--- core/vm/evm.go | 4 +-- core/vm/instructions.go | 6 ++--- core/vm/operations_verkle.go | 4 +-- trie/utils/verkle.go | 12 ++++----- trie/utils/verkle_test.go | 2 +- trie/verkle.go | 6 ++--- 10 files changed, 66 insertions(+), 94 deletions(-) delete mode 100644 core/state/accesslist/types.go diff --git a/core/state/access_witness.go b/core/state/access_witness.go index 590ef2558dbc..61b674f19c4f 100644 --- a/core/state/access_witness.go +++ b/core/state/access_witness.go @@ -97,14 +97,6 @@ func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { return gas } -func (aw *AccessWitness) TouchAndChargeProofOfAbsence(addr []byte) uint64 { - var gas uint64 - for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), false) - } - return gas -} - func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) @@ -131,17 +123,6 @@ func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSen return gas } -// TouchAndChargeContractCreateCompleted charges access access costs after -// the completion of a contract creation to populate the created account in -// the tree -func (aw *AccessWitness) TouchAndChargeContractCreateCompleted(addr []byte) uint64 { - var gas uint64 - for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), true) - } - return gas -} - func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) @@ -157,7 +138,7 @@ func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 { func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 { aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, false) aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false) - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false) aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, false) if sendsValue { aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) @@ -172,14 +153,9 @@ func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsVa return 0 } -func (aw *AccessWitness) TouchAddressOnWriteAndComputeGas(addr []byte, slot common.Hash) uint64 { - treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes()) - return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, true) -} - -func (aw *AccessWitness) TouchAddressOnReadAndComputeGas(addr []byte, slot common.Hash) uint64 { +func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool) uint64 { treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes()) - return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, false) + return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) } func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { @@ -317,5 +293,5 @@ func (aw *AccessWitness) TouchCodeSize(addr []byte, isWrite bool) uint64 { } func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) uint64 { - return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite) } diff --git a/core/state/accesslist/types.go b/core/state/accesslist/types.go deleted file mode 100644 index 53d5e03198b7..000000000000 --- a/core/state/accesslist/types.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 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 accesslist - -// AccessType specifies if a state access is for reading or writing. This -// is necessary because verkle charges different costs depending on the -// type of access. -type AccessType bool - -var ( - AccessListRead = AccessType(false) - AccessListWrite = AccessType(true) -) - -// ItemType is used in verkle mode to specify what item of an account -// is being accessed. This is necessary because verkle charges gas each -// time a new account item is accessed. -type ItemType uint64 - -const ( - Version = ItemType(1 << iota) - Balance - Nonce - CodeHash - CodeSize - LastHeaderItem -) - -const ALAllItems = Version | Balance | Nonce | CodeSize | CodeHash -const ALNoItems = ItemType(0) diff --git a/core/state_processor.go b/core/state_processor.go index a30cfab60285..eb5258f76fdf 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -260,7 +260,7 @@ func (kvm *keyValueMigrator) addAccount(addr []byte, acc *types.StateAccount) { binary.LittleEndian.PutUint64(nonce[:8], acc.Nonce) leafNodeData.Values[tutils.NonceLeafKey] = nonce[:] - leafNodeData.Values[tutils.CodeKeccakLeafKey] = acc.CodeHash[:] + leafNodeData.Values[tutils.CodeHashLeafKey] = acc.CodeHash[:] } func (kvm *keyValueMigrator) addAccountCode(addr []byte, codeSize uint64, chunks []byte) { @@ -381,5 +381,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().TouchAddressOnWriteAndComputeGas(params.HistoryStorageAddress[:], key) + statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true) } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index b72147d70a98..623259f03870 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -20,6 +20,9 @@ import ( "bytes" "crypto/ecdsa" "encoding/binary" + "encoding/json" + "fmt" + "os" //"fmt" "math/big" @@ -37,7 +40,9 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie/utils" //"github.com/ethereum/go-ethereum/rlp" @@ -472,11 +477,19 @@ func TestProcessVerkle(t *testing.T) { }, }, } + loggerCfg = &logger.Config{} ) + + os.MkdirAll("output", 0755) + traceFile, err := os.Create("./output/traces.jsonl") + if err != nil { + t.Fatal(err) + } + // Verkle trees use the snapshot, which must be enabled before the // data is saved into the tree+database. genesis := gspec.MustCommit(bcdb) - blockchain, _ := NewBlockChain(bcdb, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(bcdb, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{Tracer: logger.NewJSONLogger(loggerCfg, traceFile)}, nil, nil) defer blockchain.Stop() // Commit the genesis block to the block-generation database as it @@ -513,6 +526,33 @@ func TestProcessVerkle(t *testing.T) { } }) + kvjson, err := json.Marshal(keyvals) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile("./output/statediffs.json", kvjson, 0644) + if err != nil { + t.Fatal(err) + } + blockrlp, err := rlp.EncodeToBytes(genesis) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile(fmt.Sprintf("./output/block%d.rlp.hex", 0), []byte(fmt.Sprintf("%x", blockrlp)), 0644) + if err != nil { + t.Fatal(err) + } + for _, block := range chain { + blockrlp, err := rlp.EncodeToBytes(block) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile(fmt.Sprintf("./output/block%d.rlp.hex", block.NumberU64()), []byte(fmt.Sprintf("%x", blockrlp)), 0644) + if err != nil { + t.Fatal(err) + } + } + // Uncomment to extract block #2 //f, _ := os.Create("block2.rlp") //defer f.Close() @@ -521,7 +561,7 @@ func TestProcessVerkle(t *testing.T) { //f.Write(buf.Bytes()) //fmt.Printf("root= %x\n", chain[0].Root()) // check the proof for the last block - err := trie.DeserializeAndVerifyVerkleProof(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), keyvals[1]) + err = trie.DeserializeAndVerifyVerkleProof(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), keyvals[1]) if err != nil { t.Fatal(err) } @@ -913,7 +953,7 @@ func TestProcessVerklExtCodeHashOpcode(t *testing.T) { } codeHashStateDiff := statediff[1][stateDiffIdx].SuffixDiffs[0] - if codeHashStateDiff.Suffix != utils.CodeKeccakLeafKey { + if codeHashStateDiff.Suffix != utils.CodeHashLeafKey { t.Fatalf("code hash invalid suffix") } if codeHashStateDiff.CurrentValue == nil { diff --git a/core/vm/evm.go b/core/vm/evm.go index 02817fbda40b..06849043873c 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -199,7 +199,7 @@ 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 - wgas := evm.Accesses.TouchAndChargeProofOfAbsence(addr.Bytes()) + wgas := evm.Accesses.TouchFullAccount(addr.Bytes(), false) if gas < wgas { evm.StateDB.RevertToSnapshot(snapshot) return nil, 0, ErrOutOfGas @@ -516,7 +516,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if len(ret) > 0 { evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret))) } - if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateCompleted(address.Bytes()[:])) { + if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true)) { err = ErrOutOfGas } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9dc465ce80f9..a12606844771 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -370,7 +370,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ contractAddr := scope.Contract.Address() paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) - if interpreter.evm.chainRules.IsPrague && !scope.Contract.IsDeployment { + if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment { statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code))) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 @@ -394,7 +394,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) uint64CodeOffset = 0xffffffffffffffff } addr := common.Address(a.Bytes20()) - if interpreter.evm.chainRules.IsPrague { + if interpreter.evm.chainRules.IsEIP4762 { code := interpreter.evm.StateDB.GetCode(addr) contract := &Contract{ Code: code, @@ -462,7 +462,7 @@ func getBlockHashFromContract(number uint64, statedb StateDB, witness *state.Acc ringIndex := number % params.Eip2935BlockHashHistorySize var pnum common.Hash binary.BigEndian.PutUint64(pnum[24:], ringIndex) - witness.TouchAddressOnReadAndComputeGas(params.HistoryStorageAddress[:], pnum) + witness.TouchSlotAndChargeGas(params.HistoryStorageAddress[:], pnum, false) return statedb.GetState(params.HistoryStorageAddress, pnum) } diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 3c4f11121394..8045c9eb1245 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -22,7 +22,7 @@ import ( ) func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.StateDB.Witness().TouchAddressOnWriteAndComputeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32())) + gas := evm.StateDB.Witness().TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -30,7 +30,7 @@ func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.StateDB.Witness().TouchAddressOnReadAndComputeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32())) + gas := evm.StateDB.Witness().TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go index d1aa9dec57fa..af1240cb16bc 100644 --- a/trie/utils/verkle.go +++ b/trie/utils/verkle.go @@ -26,11 +26,11 @@ import ( ) const ( - VersionLeafKey = 0 - BalanceLeafKey = 1 - NonceLeafKey = 2 - CodeKeccakLeafKey = 3 - CodeSizeLeafKey = 4 + VersionLeafKey = 0 + BalanceLeafKey = 1 + NonceLeafKey = 2 + CodeHashLeafKey = 3 + CodeSizeLeafKey = 4 maxPointCacheByteSize = 100 << 20 ) @@ -152,7 +152,7 @@ func GetTreeKeyNonce(address []byte) []byte { } func GetTreeKeyCodeKeccak(address []byte) []byte { - return GetTreeKey(address, zero, CodeKeccakLeafKey) + return GetTreeKey(address, zero, CodeHashLeafKey) } func GetTreeKeyCodeSize(address []byte) []byte { diff --git a/trie/utils/verkle_test.go b/trie/utils/verkle_test.go index 02de69c9b712..35cae6e03349 100644 --- a/trie/utils/verkle_test.go +++ b/trie/utils/verkle_test.go @@ -77,7 +77,7 @@ func sha256GetTreeKeyCodeSize(addr []byte) []byte { copy(payload[:len(treeIndexBytes)], treeIndexBytes) digest.Write(payload[:]) h := digest.Sum(nil) - h[31] = CodeKeccakLeafKey + h[31] = CodeHashLeafKey return h } diff --git a/trie/verkle.go b/trie/verkle.go index 760e30c8cdaa..ee953c232318 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -125,7 +125,7 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error // been recreated after that, then its code keccak will NOT be 0. So return `nil` if // the nonce, and values[10], and code keccak is 0. - if acc.Nonce == 0 && len(values) > 10 && len(values[10]) > 0 && bytes.Equal(values[utils.CodeKeccakLeafKey], zero[:]) { + if acc.Nonce == 0 && len(values) > 10 && len(values[10]) > 0 && bytes.Equal(values[utils.CodeHashLeafKey], zero[:]) { if !t.ended { return nil, errDeletedAccount } else { @@ -144,7 +144,7 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error // } // } acc.Balance = new(big.Int).SetBytes(balance[:]) - acc.CodeHash = values[utils.CodeKeccakLeafKey] + acc.CodeHash = values[utils.CodeHashLeafKey] // TODO fix the code size as well return acc, nil @@ -164,7 +164,7 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount) values[utils.VersionLeafKey] = zero[:] values[utils.NonceLeafKey] = nonce[:] values[utils.BalanceLeafKey] = balance[:] - values[utils.CodeKeccakLeafKey] = acc.CodeHash[:] + values[utils.CodeHashLeafKey] = acc.CodeHash[:] binary.LittleEndian.PutUint64(nonce[:], acc.Nonce) bbytes := acc.Balance.Bytes() From b2609706f53ab087d89ff62dbaa4705147ef03fe Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sun, 17 Mar 2024 20:28:01 +0100 Subject: [PATCH 03/25] Apply suggestions from code review Co-authored-by: Ignacio Hagopian --- core/vm/instructions.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index a12606844771..b13b28d85787 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -635,7 +635,9 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b if interpreter.evm.chainRules.IsEIP4762 { contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], value.Sign() != 0) - scope.Contract.UseGas(statelessGas) + if !scope.Contract.UseGas(statelessGas) { + return nil, ErrExecutionReverted + } } // reuse size int for stackvalue @@ -686,7 +688,9 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] codeAndHash := &codeAndHash{code: input} contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], endowment.Sign() != 0) - scope.Contract.UseGas(statelessGas) + if !scope.Contract.UseGas(statelessGas) { + return nil, ErrExecutionReverted + } } // Apply EIP150 From 1df183a7a53e8b07080bfcb7138a09c2daf1edc9 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sun, 17 Mar 2024 20:28:20 +0100 Subject: [PATCH 04/25] more suggestions from code review --- core/vm/instructions.go | 2 ++ core/vm/operations_verkle.go | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index b13b28d85787..0e93ebfba3f4 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -402,6 +402,8 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code))) + statelessGas += interpreter.evm.Accesses.TouchVersion(addr[:], false) + statelessGas += interpreter.evm.Accesses.TouchCodeSize(addr[:], false) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 8045c9eb1245..e57175612e18 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -44,7 +44,8 @@ func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - return evm.StateDB.Witness().TouchCodeSize(address[:], false), nil + versiongas := evm.StateDB.Witness().TouchVersion(address[:], false) + return versiongas + evm.StateDB.Witness().TouchCodeSize(address[:], false), nil } func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { From df69fece5c46365db3d36634453a4ce1ae435409 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:24:48 +0100 Subject: [PATCH 05/25] don't charge creation gas + charge code chunks in create --- core/state_processor_test.go | 4 ++-- core/vm/evm.go | 28 +++++++++++++++++----------- core/vm/instructions.go | 8 ++++---- core/vm/interpreter.go | 2 +- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 623259f03870..20155ba76427 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -498,8 +498,8 @@ func TestProcessVerkle(t *testing.T) { txCost1 := params.TxGas txCost2 := params.TxGas - contractCreationCost := intrinsicContractCreationGas + uint64(5600+700+700+700 /* creation with value */ +2739 /* execution costs */) - codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +198644 /* execution costs */) + contractCreationCost := intrinsicContractCreationGas + uint64(5600+700+700+700 /* creation with value */ +1439 /* execution costs */) + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +56344 /* execution costs */) blockGasUsagesExpected := []uint64{ txCost1*2 + txCost2, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, diff --git a/core/vm/evm.go b/core/vm/evm.go index 06849043873c..2bfc2e233264 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -504,20 +504,26 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // be stored due to not enough gas set an error and let it be handled // by the error checking condition below. if err == nil { - createDataGas := uint64(len(ret)) * params.CreateDataGas - if contract.UseGas(createDataGas) { - evm.StateDB.SetCode(address, ret) + if !evm.chainRules.IsEIP4762 { + createDataGas := uint64(len(ret)) * params.CreateDataGas + if contract.UseGas(createDataGas) { + } else { + err = ErrCodeStoreOutOfGas + } } else { - err = ErrCodeStoreOutOfGas + // Contract creation completed, touch the missing field in the contract + if len(ret) > 0 { + if !contract.UseGas(evm.Accesses.TouchCodeChunkRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { + err = ErrOutOfGas + } + } + if err == nil && !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true)) { + err = ErrOutOfGas + } } - } - if err == nil && evm.chainRules.IsEIP4762 { - if len(ret) > 0 { - evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret))) - } - if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true)) { - err = ErrOutOfGas + if err == nil { + evm.StateDB.SetCode(address, ret) } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 0e93ebfba3f4..f554ee891ae4 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -371,7 +371,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ contractAddr := scope.Contract.Address() paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment { - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code))) + statelessGas := interpreter.evm.Accesses.TouchCodeChunkRangeAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -401,7 +401,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code))) + statelessGas := interpreter.evm.Accesses.TouchCodeChunkRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) statelessGas += interpreter.evm.Accesses.TouchVersion(addr[:], false) statelessGas += interpreter.evm.Accesses.TouchCodeSize(addr[:], false) if !scope.Contract.UseGas(statelessGas) { @@ -954,7 +954,7 @@ 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. contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code))) + statelessGas := interpreter.evm.Accesses.TouchCodeChunkRangeAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -983,7 +983,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsPrague { contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code))) + statelessGas := interpreter.evm.Accesses.TouchCodeChunkRangeAndChargeGas(contractAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 93750de2ca3a..eb52a2080e69 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -183,7 +183,7 @@ 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() - contract.Gas -= in.evm.TxContext.Accesses.TouchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], pc, 1, uint64(len(contract.Code))) + contract.Gas -= in.evm.TxContext.Accesses.TouchCodeChunkRangeAndChargeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) } // Get the operation from the jump table and validate the stack to ensure there are From 93ebf5725e294dc50e39fe8a3fa3d98e8f856ee2 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:43:41 +0100 Subject: [PATCH 06/25] A couple more fixes --- core/state/access_witness.go | 4 ++-- core/vm/evm.go | 5 ++++- core/vm/instructions.go | 8 ++++---- core/vm/interpreter.go | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/core/state/access_witness.go b/core/state/access_witness.go index 61b674f19c4f..4c9d3218ad96 100644 --- a/core/state/access_witness.go +++ b/core/state/access_witness.go @@ -242,7 +242,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) TouchCodeChunksRangeOnReadAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64) uint64 { +func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) 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 @@ -265,7 +265,7 @@ func (aw *AccessWitness) TouchCodeChunksRangeOnReadAndChargeGas(contractAddr []b for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) subIndex := byte((chunkNumber + 128) % 256) - gas := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, false) + gas := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) var overflow bool statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) if overflow { diff --git a/core/vm/evm.go b/core/vm/evm.go index 2bfc2e233264..49cbd1a3b8f5 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -459,6 +459,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.chainRules.IsEIP2929 { evm.StateDB.AddAddressToAccessList(address) } + if evm.chainRules.IsEIP4762 { + evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() == 0) + } // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { @@ -513,7 +516,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } else { // Contract creation completed, touch the missing field in the contract if len(ret) > 0 { - if !contract.UseGas(evm.Accesses.TouchCodeChunkRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { + if !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { err = ErrOutOfGas } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index f554ee891ae4..e8928d561fc3 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -371,7 +371,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ contractAddr := scope.Contract.Address() paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment { - statelessGas := interpreter.evm.Accesses.TouchCodeChunkRangeAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -401,7 +401,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := interpreter.evm.Accesses.TouchCodeChunkRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) statelessGas += interpreter.evm.Accesses.TouchVersion(addr[:], false) statelessGas += interpreter.evm.Accesses.TouchCodeSize(addr[:], false) if !scope.Contract.UseGas(statelessGas) { @@ -954,7 +954,7 @@ 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. contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.TouchCodeChunkRangeAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -983,7 +983,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsPrague { contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.TouchCodeChunkRangeAndChargeGas(contractAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index eb52a2080e69..46bbfd0105f7 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -183,7 +183,7 @@ 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() - contract.Gas -= in.evm.TxContext.Accesses.TouchCodeChunkRangeAndChargeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) + contract.Gas -= in.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) } // Get the operation from the jump table and validate the stack to ensure there are From ad0717d9034e7d939b00f67d6b006ab5adda5c46 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:46:05 +0100 Subject: [PATCH 07/25] make linter happy --- core/vm/evm.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 49cbd1a3b8f5..68b654e9eea0 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -208,7 +208,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { - // Calling a non existing account, don't do anything, but ping the tracer if debug { if evm.depth == 0 { From 5c91ceeeea299047be66454797122e0263f0d034 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:06:49 +0100 Subject: [PATCH 08/25] fix create init gas consumption issue --- core/vm/evm.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 68b654e9eea0..f4b94ececa54 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -458,9 +458,6 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.chainRules.IsEIP2929 { evm.StateDB.AddAddressToAccessList(address) } - if evm.chainRules.IsEIP4762 { - evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() == 0) - } // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { @@ -481,6 +478,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, contract.SetCodeOptionalHash(&address, codeAndHash) contract.IsDeployment = true + // Charge the contract creation init gas in verkle mode + var err error + if evm.chainRules.IsEIP4762 { + if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() != 0)) { + err = ErrOutOfGas + } + } + if evm.Config.Tracer != nil { if evm.depth == 0 { evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) @@ -489,7 +494,8 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } - ret, err := evm.interpreter.Run(contract, nil, false) + var ret []byte + ret, err = evm.interpreter.Run(contract, nil, false) // Check whether the max code size has been exceeded, assign err if the case. if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { From 0ebb629d3d6f2f095a932570fd5d62d158fa0635 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:16:47 +0100 Subject: [PATCH 09/25] fix: in gas funcs, use tx witness instead of global witness --- core/state_processor_test.go | 2 +- core/vm/operations_verkle.go | 22 ++++++++-------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 20155ba76427..25a7d28fea61 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -499,7 +499,7 @@ func TestProcessVerkle(t *testing.T) { txCost1 := params.TxGas txCost2 := params.TxGas contractCreationCost := intrinsicContractCreationGas + uint64(5600+700+700+700 /* creation with value */ +1439 /* execution costs */) - codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +56344 /* execution costs */) + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +46544 /* execution costs */) blockGasUsagesExpected := []uint64{ txCost1*2 + txCost2, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index e57175612e18..bc255480fe85 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -22,7 +22,7 @@ import ( ) func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.StateDB.Witness().TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) + gas := evm.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -30,7 +30,7 @@ func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.StateDB.Witness().TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) + gas := evm.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -39,18 +39,18 @@ func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - return evm.StateDB.Witness().TouchBalance(address[:], false), nil + return evm.Accesses.TouchBalance(address[:], false), nil } func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - versiongas := evm.StateDB.Witness().TouchVersion(address[:], false) - return versiongas + evm.StateDB.Witness().TouchCodeSize(address[:], false), nil + versiongas := evm.Accesses.TouchVersion(address[:], false) + return versiongas + evm.Accesses.TouchCodeSize(address[:], false), nil } func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - return evm.StateDB.Witness().TouchCodeHash(address[:], false), nil + return evm.Accesses.TouchCodeHash(address[:], false), nil } func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { @@ -59,15 +59,9 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { if err != nil { return 0, err } - wgas, err := evm.StateDB.Witness().TouchCodeSize(contract.Address().Bytes(), false), nil - if err != nil { - return 0, err - } + wgas := evm.Accesses.TouchCodeSize(contract.Address().Bytes(), false) gas += wgas - wgas, err = evm.StateDB.Witness().TouchCodeHash(contract.Address().Bytes(), false), nil - if err != nil { - return 0, err - } + wgas = evm.Accesses.TouchCodeHash(contract.Address().Bytes(), false) return wgas + gas, nil } } From 090794e8b0048d941ee6d28f52aee1b02019733e Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:32:21 +0100 Subject: [PATCH 10/25] fix linter issue --- core/vm/evm.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index f4b94ececa54..b890ce9723b6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -495,7 +495,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } var ret []byte - ret, err = evm.interpreter.Run(contract, nil, false) + if err == nil { + ret, err = evm.interpreter.Run(contract, nil, false) + } // Check whether the max code size has been exceeded, assign err if the case. if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { From 87a89da0c1a501f693a5cc8418c11f1dd75d1a2d Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:33:04 +0100 Subject: [PATCH 11/25] Apply suggestions from code review Co-authored-by: Ignacio Hagopian --- core/vm/evm.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index b890ce9723b6..2ee5728960d2 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -516,17 +516,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if err == nil { if !evm.chainRules.IsEIP4762 { createDataGas := uint64(len(ret)) * params.CreateDataGas - if contract.UseGas(createDataGas) { - } else { + if !contract.UseGas(createDataGas) { err = ErrCodeStoreOutOfGas } } else { // Contract creation completed, touch the missing field in the contract - if len(ret) > 0 { - if !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { + if len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { err = ErrOutOfGas } - } if err == nil && !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true)) { err = ErrOutOfGas } From c5264bd8d2433d473c7904882919aec469c74584 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:17:12 +0100 Subject: [PATCH 12/25] fix: EXTCODECOPY gas consumption --- core/state_processor_test.go | 2 +- core/vm/eips.go | 1 + core/vm/operations_verkle.go | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 25a7d28fea61..991f4e2de68e 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -499,7 +499,7 @@ func TestProcessVerkle(t *testing.T) { txCost1 := params.TxGas txCost2 := params.TxGas contractCreationCost := intrinsicContractCreationGas + uint64(5600+700+700+700 /* creation with value */ +1439 /* execution costs */) - codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +46544 /* execution costs */) + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +44044 /* execution costs */) blockGasUsagesExpected := []uint64{ txCost1*2 + txCost2, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, diff --git a/core/vm/eips.go b/core/vm/eips.go index 2703c8316db2..128bd13044e3 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -313,6 +313,7 @@ func enable4762(jt *JumpTable) { jt[BALANCE].dynamicGas = gasBalance4762 jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762 jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762 jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762 jt[CREATE].constantGas = params.CreateNGasEip4762 jt[CREATE2].constantGas = params.CreateNGasEip4762 diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index bc255480fe85..ec7c3bea6dfc 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -18,6 +18,7 @@ package vm import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" ) @@ -91,3 +92,20 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem } return 0, 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()) + wgas := evm.Accesses.TouchVersion(addr[:], false) + wgas += evm.Accesses.TouchCodeSize(addr[:], false) + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, wgas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} From 26ad438cb6906db8a95a882bf001a3d74ab03601 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:57:04 +0100 Subject: [PATCH 13/25] fix warm gas costs --- core/vm/eips.go | 8 ++++++++ core/vm/gas_table.go | 12 ++++++------ core/vm/instructions.go | 2 -- core/vm/interpreter.go | 2 +- core/vm/operations_verkle.go | 30 ++++++++++++++++++++++++------ 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 128bd13044e3..1df79f6cffd0 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -311,14 +311,22 @@ func enable4762(jt *JumpTable) { jt[SLOAD].constantGas = 0 jt[SLOAD].dynamicGas = gasSLoad4762 jt[BALANCE].dynamicGas = gasBalance4762 + jt[BALANCE].constantGas = 0 + jt[EXTCODESIZE].constantGas = 0 jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762 + jt[EXTCODEHASH].constantGas = 0 jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 + jt[EXTCODECOPY].constantGas = 0 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 = gasCallEIP4762 + jt[CALLCODE].constantGas = 0 jt[CALLCODE].dynamicGas = gasCallCodeEIP4762 + jt[STATICCALL].constantGas = 0 jt[STATICCALL].dynamicGas = gasStaticCallEIP4762 + jt[DELEGATECALL].constantGas = 0 jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP4762 } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 64b6df5cb454..d1ffaf6b60f9 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -391,7 +391,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } else if !evm.StateDB.Exist(address) { gas += params.CallNewAccountGas } - if transfersValue { + if transfersValue && !evm.chainRules.IsEIP4762 { gas += params.CallValueTransferGas } memoryGas, err := memoryGasCost(mem, memorySize) @@ -410,7 +410,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } - if evm.chainRules.IsPrague { + if evm.chainRules.IsEIP4762 { if _, isPrecompile := evm.precompile(address); !isPrecompile { gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()[:])) if overflow { @@ -437,7 +437,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory gas uint64 overflow bool ) - if stack.Back(2).Sign() != 0 { + if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { gas += params.CallValueTransferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { @@ -450,7 +450,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } - if evm.chainRules.IsPrague { + if evm.chainRules.IsEIP4762 { address := common.Address(stack.Back(1).Bytes20()) if _, isPrecompile := evm.precompile(address); !isPrecompile { gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) @@ -475,7 +475,7 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } - if evm.chainRules.IsPrague { + if evm.chainRules.IsEIP4762 { address := common.Address(stack.Back(1).Bytes20()) if _, isPrecompile := evm.precompile(address); !isPrecompile { gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) @@ -500,7 +500,7 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } - if evm.chainRules.IsPrague { + if evm.chainRules.IsEIP4762 { address := common.Address(stack.Back(1).Bytes20()) if _, isPrecompile := evm.precompile(address); !isPrecompile { gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index e8928d561fc3..7760916e1635 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -402,8 +402,6 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) - statelessGas += interpreter.evm.Accesses.TouchVersion(addr[:], false) - statelessGas += interpreter.evm.Accesses.TouchCodeSize(addr[:], false) if !scope.Contract.UseGas(statelessGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 46bbfd0105f7..427ac4e44fc5 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -179,7 +179,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged, pcCopy, gasCopy = false, pc, contract.Gas } - if in.evm.chainRules.IsPrague && !contract.IsDeployment { + if in.evm.chainRules.IsEIP4762 && !contract.IsDeployment { // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index ec7c3bea6dfc..a43a86e71159 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -40,18 +40,31 @@ func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - return evm.Accesses.TouchBalance(address[:], false), nil + gas := evm.Accesses.TouchBalance(address[:], false) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil } func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - versiongas := evm.Accesses.TouchVersion(address[:], false) - return versiongas + evm.Accesses.TouchCodeSize(address[:], false), nil + wgas := evm.Accesses.TouchVersion(address[:], false) + wgas += evm.Accesses.TouchCodeSize(address[:], false) + if wgas == 0 { + wgas = params.WarmStorageReadCostEIP2929 + } + + return wgas, nil } func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - return evm.Accesses.TouchCodeHash(address[:], false), nil + codehashgas := evm.Accesses.TouchCodeHash(address[:], false) + if codehashgas == 0 { + codehashgas = params.WarmStorageReadCostEIP2929 + } + return codehashgas, nil } func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { @@ -61,8 +74,10 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { return 0, err } wgas := evm.Accesses.TouchCodeSize(contract.Address().Bytes(), false) - gas += wgas - wgas = evm.Accesses.TouchCodeHash(contract.Address().Bytes(), false) + wgas += evm.Accesses.TouchCodeHash(contract.Address().Bytes(), false) + if wgas == 0 { + wgas = params.WarmStorageReadCostEIP2929 + } return wgas + gas, nil } } @@ -102,6 +117,9 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo addr := common.Address(stack.peek().Bytes20()) wgas := evm.Accesses.TouchVersion(addr[:], false) wgas += evm.Accesses.TouchCodeSize(addr[:], false) + if wgas == 0 { + wgas = params.WarmStorageReadCostEIP2929 + } var overflow bool // We charge (cold-warm), since 'warm' is already charged as constantGas if gas, overflow = math.SafeAdd(gas, wgas); overflow { From 9d7d5bc9bc3f0ea27617c918a0fb4c939005954b Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 17:10:34 +0100 Subject: [PATCH 14/25] fix the order gas is charged in during contract creation epilogue --- core/vm/evm.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 2ee5728960d2..c95401582050 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -520,11 +520,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, err = ErrCodeStoreOutOfGas } } else { - // Contract creation completed, touch the missing field in the contract - if len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { - err = ErrOutOfGas - } - if err == nil && !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true)) { + // Contract creation completed, touch the missing fields in the contract + if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true)) { + err = ErrOutOfGas + } + + if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { err = ErrOutOfGas } } From 30eea50ad090e56028603db148d7032a21c94f29 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:38:33 +0100 Subject: [PATCH 15/25] fix selfdestruct --- core/vm/operations_verkle.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index a43a86e71159..70219a5a316d 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -92,20 +92,20 @@ var ( func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { beneficiaryAddr := common.Address(stack.peek().Bytes20()) contractAddr := contract.Address() - // If the beneficiary isn't the contract, we need to touch the beneficiary's balance. - // If the beneficiary is the contract itself, there're two possibilities: - // 1. The contract was created in the same transaction: the balance is already touched (no need to touch again) - // 2. The contract wasn't created in the same transaction: there's no net change in balance, - // and SELFDESTRUCT will perform no action on the account header. (we touch since we did SubBalance+AddBalance above) - if contractAddr != beneficiaryAddr || evm.StateDB.WasCreatedInCurrentTx(contractAddr) { - statelessGas := evm.Accesses.TouchBalance(beneficiaryAddr[:], false) - if !contract.UseGas(statelessGas) { - contract.Gas = 0 - return 0, ErrOutOfGas + statelessGas := evm.Accesses.TouchVersion(contractAddr[:], false) + statelessGas += evm.Accesses.TouchCodeSize(contractAddr[:], false) + statelessGas += evm.Accesses.TouchBalance(contractAddr[:], false) + if contractAddr != beneficiaryAddr { + statelessGas += evm.Accesses.TouchBalance(beneficiaryAddr[:], false) + } + // Charge write costs if it transfers value + if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { + statelessGas += evm.Accesses.TouchBalance(contractAddr[:], true) + if contractAddr != beneficiaryAddr { + statelessGas += evm.Accesses.TouchBalance(beneficiaryAddr[:], true) } - return statelessGas, nil } - return 0, nil + return statelessGas, nil } func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { @@ -127,3 +127,4 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo } return gas, nil } + From f89dc814e6d3de79fc807514f41b026cf3c3222c Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:32:16 +0100 Subject: [PATCH 16/25] fix #365 in eip rewrite (#407) --- core/state_processor.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/state_processor.go b/core/state_processor.go index eb5258f76fdf..fbc6beda4a08 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -368,7 +368,13 @@ func (kvm *keyValueMigrator) migrateCollectedKeyValues(tree *trie.VerkleTrie) er return nil } +// InsertBlockHashHistoryAtEip2935Fork handles the insertion of all previous 256 +// blocks on the eip2935 activation block. It also adds the account header of the +// history contract to the witness. 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) + ancestor := chain.GetHeader(prevHash, prevNumber) for i := prevNumber; i > 0 && i >= prevNumber-params.Eip2935BlockHashHistorySize; i-- { ProcessParentBlockHash(statedb, i, ancestor.Hash()) From b5e2ba62c39924f70f780a044e06461e36732d48 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 3 Apr 2024 18:30:11 +0200 Subject: [PATCH 17/25] fix: OOG type in code creation OOG (#408) --- core/vm/evm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index c95401582050..a8ec9f7a061a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -522,11 +522,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } else { // Contract creation completed, touch the missing fields in the contract if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true)) { - err = ErrOutOfGas + err = ErrCodeStoreOutOfGas } if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { - err = ErrOutOfGas + err = ErrCodeStoreOutOfGas } } From ccb4692ceb386d2cfe1270672b9fbb9b4efa1e5b Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 5 Apr 2024 12:23:40 -0300 Subject: [PATCH 18/25] core/vm: charge BLOCKHASH witness cost (#409) * core/vm: charge BLOCKHASH witness cost Signed-off-by: Ignacio Hagopian * remove gas optimization for now Signed-off-by: Ignacio Hagopian --------- Signed-off-by: Ignacio Hagopian --- core/vm/instructions.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7760916e1635..ef3472d8acd9 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -458,12 +458,12 @@ func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ return nil, nil } -func getBlockHashFromContract(number uint64, statedb StateDB, witness *state.AccessWitness) common.Hash { +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) - witness.TouchSlotAndChargeGas(params.HistoryStorageAddress[:], pnum, false) - return statedb.GetState(params.HistoryStorageAddress, pnum) + statelessGas := witness.TouchSlotAndChargeGas(params.HistoryStorageAddress[:], pnum, false) + return statedb.GetState(params.HistoryStorageAddress, pnum), statelessGas } func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { @@ -486,7 +486,13 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( if num64 >= lower && num64 < upper { // if Prague is active, read it from the history contract (EIP 2935). if evm.chainRules.IsPrague { - num.SetBytes(getBlockHashFromContract(num64, evm.StateDB, evm.Accesses).Bytes()) + blockHash, statelessGas := getBlockHashFromContract(num64, evm.StateDB, evm.Accesses) + if interpreter.evm.chainRules.IsEIP4762 { + if !scope.Contract.UseGas(statelessGas) { + return nil, ErrExecutionReverted + } + } + num.SetBytes(blockHash.Bytes()) } else { num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) } From 48c51edbb21b17765e670d9aa71fffe732241a39 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Mon, 8 Apr 2024 16:12:36 -0300 Subject: [PATCH 19/25] remove redundant logic for contract creation (#413) Signed-off-by: Ignacio Hagopian --- core/state_transition.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 49b35dd81087..0aa4a369018b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -27,7 +27,6 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" ) @@ -411,7 +410,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if !tryConsumeGas(&st.gasRemaining, statelessGasOrigin) { return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) } - originNonce := st.evm.StateDB.GetNonce(originAddr) if msg.To != nil { statelessGasDest := st.evm.Accesses.TouchTxExistingAndComputeGas(targetAddr.Bytes(), msg.Value.Sign() != 0) @@ -421,11 +419,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // ensure the code size ends up in the access witness st.evm.StateDB.GetCodeSize(*targetAddr) - } else { - contractAddr := crypto.CreateAddress(originAddr, originNonce) - if !tryConsumeGas(&st.gasRemaining, st.evm.Accesses.TouchAndChargeContractCreateInit(contractAddr.Bytes(), msg.Value.Sign() != 0)) { - return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) - } } } From 33ff60f7ab4a2fe5062ef659ebd46e6125b7cd22 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 9 Apr 2024 06:34:13 -0300 Subject: [PATCH 20/25] fix precompile address check for charging witness costs & fix missing value-bearing rule (#412) Signed-off-by: Ignacio Hagopian --- core/vm/gas_table.go | 29 +++-------------------------- core/vm/operations_verkle.go | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index d1ffaf6b60f9..5f8183801f1c 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -411,12 +411,6 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return 0, ErrGasUintOverflow } if evm.chainRules.IsEIP4762 { - if _, isPrecompile := evm.precompile(address); !isPrecompile { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()[:])) - if overflow { - return 0, ErrGasUintOverflow - } - } if transfersValue { gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { @@ -452,8 +446,9 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory } if evm.chainRules.IsEIP4762 { address := common.Address(stack.Back(1).Bytes20()) - if _, isPrecompile := evm.precompile(address); !isPrecompile { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) + transfersValue := !stack.Back(2).IsZero() + if transfersValue { + gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } @@ -475,15 +470,6 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } - if evm.chainRules.IsEIP4762 { - address := common.Address(stack.Back(1).Bytes20()) - if _, isPrecompile := evm.precompile(address); !isPrecompile { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) - if overflow { - return 0, ErrGasUintOverflow - } - } - } return gas, nil } @@ -500,15 +486,6 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } - if evm.chainRules.IsEIP4762 { - address := common.Address(stack.Back(1).Bytes20()) - if _, isPrecompile := evm.precompile(address); !isPrecompile { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) - if overflow { - return 0, ErrGasUintOverflow - } - } - } return gas, nil } diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 70219a5a316d..152b8a9646a2 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -49,17 +49,22 @@ func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem 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 0, nil + } wgas := evm.Accesses.TouchVersion(address[:], false) wgas += evm.Accesses.TouchCodeSize(address[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } - return wgas, 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 { + return 0, nil + } codehashgas := evm.Accesses.TouchCodeHash(address[:], false) if codehashgas == 0 { codehashgas = params.WarmStorageReadCostEIP2929 @@ -73,8 +78,10 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { if err != nil { return 0, err } - wgas := evm.Accesses.TouchCodeSize(contract.Address().Bytes(), false) - wgas += evm.Accesses.TouchCodeHash(contract.Address().Bytes(), false) + if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { + return gas, nil + } + wgas := evm.Accesses.TouchAndChargeMessageCall(contract.Address().Bytes()) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -91,6 +98,10 @@ var ( func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { beneficiaryAddr := common.Address(stack.peek().Bytes20()) + if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile { + return 0, nil + } + contractAddr := contract.Address() statelessGas := evm.Accesses.TouchVersion(contractAddr[:], false) statelessGas += evm.Accesses.TouchCodeSize(contractAddr[:], false) @@ -127,4 +138,3 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo } return gas, nil } - From 342722e96614de57820f6b31d1b9e5ebccf906b0 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 9 Apr 2024 09:42:18 -0300 Subject: [PATCH 21/25] core/vm: fix wrong check (#416) Signed-off-by: Ignacio Hagopian --- core/vm/evm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index a8ec9f7a061a..3278856f39a8 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -525,7 +525,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, err = ErrCodeStoreOutOfGas } - if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { + if err == nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { err = ErrCodeStoreOutOfGas } } From ea20444ea3e554e21e32621bb9a380417ca6d5cb Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:21:30 +0200 Subject: [PATCH 22/25] charge for account creation if selfdestruct creates a new account (#417) --- core/vm/operations_verkle.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 152b8a9646a2..8b55b83bb21f 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -115,6 +115,13 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem if contractAddr != beneficiaryAddr { statelessGas += evm.Accesses.TouchBalance(beneficiaryAddr[:], true) } + + // Case when the beneficiary does not exist: touch the account + // but leave code hash and size alone. + if evm.StateDB.Empty(beneficiaryAddr) { + statelessGas += evm.Accesses.TouchVersion(beneficiaryAddr[:], true) + statelessGas += evm.Accesses.TouchNonce(beneficiaryAddr[:], true) + } } return statelessGas, nil } From 3a40da05e00e74f94dbbe519739be8069666d024 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:10:44 +0200 Subject: [PATCH 23/25] add key comparison test (#418) --- trie/utils/verkle_test.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/trie/utils/verkle_test.go b/trie/utils/verkle_test.go index 35cae6e03349..ea6b733feddb 100644 --- a/trie/utils/verkle_test.go +++ b/trie/utils/verkle_test.go @@ -17,10 +17,11 @@ package utils import ( + "bytes" + "crypto/rand" "crypto/sha256" "encoding/hex" "math/big" - "math/rand" "testing" "github.com/ethereum/go-verkle" @@ -93,3 +94,22 @@ func BenchmarkSha256Hash(b *testing.B) { sha256GetTreeKeyCodeSize(addr[:]) } } + +func TestCompareGetTreeKeyWithEvaluated(t *testing.T) { + var addr [32]byte + rand.Read(addr[:]) + addrpoint := EvaluateAddressPoint(addr[:]) + for i := 0; i < 100; i++ { + var val [32]byte + rand.Read(val[:]) + n := uint256.NewInt(0).SetBytes(val[:]) + n.Lsh(n, 8) + subindex := byte(val[0]) + tk1 := GetTreeKey(addr[:], n, subindex) + tk2 := GetTreeKeyWithEvaluatedAddess(addrpoint, n, subindex) + + if !bytes.Equal(tk1, tk2) { + t.Fatalf("differing key: slot=%x, addr=%x", val, addr) + } + } +} From 29337311247ad8b923f61018de02835b9f5c4b86 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 12 Apr 2024 05:20:34 -0300 Subject: [PATCH 24/25] core/vm: charge contract init before execution logic (#419) * core/vm: charge contract init before execution logic Signed-off-by: Ignacio Hagopian * fix CREATE2 as well --------- Signed-off-by: Ignacio Hagopian Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- core/vm/instructions.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index ef3472d8acd9..7a6db2e8eaaa 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -628,16 +628,8 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b if interpreter.readOnly { return nil, ErrWriteProtection } - var ( - value = scope.Stack.pop() - offset, size = scope.Stack.pop(), scope.Stack.pop() - input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) - gas = scope.Contract.Gas - ) - if interpreter.evm.chainRules.IsEIP150 { - gas -= gas / 64 - } + value := scope.Stack.pop() if interpreter.evm.chainRules.IsEIP4762 { contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], value.Sign() != 0) @@ -646,6 +638,15 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } } + var ( + offset, size = scope.Stack.pop(), scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas + ) + if interpreter.evm.chainRules.IsEIP150 { + gas -= gas / 64 + } + // reuse size int for stackvalue stackvalue := size @@ -688,7 +689,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] offset, size = scope.Stack.pop(), scope.Stack.pop() salt = scope.Stack.pop() input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) - gas = scope.Contract.Gas ) if interpreter.evm.chainRules.IsEIP4762 { codeAndHash := &codeAndHash{code: input} @@ -699,6 +699,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] } } + var gas = scope.Contract.Gas // Apply EIP150 gas -= gas / 64 scope.Contract.UseGas(gas) From 06a92923b306fa4d669caafc92994d3433a09bd2 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:38:01 +0200 Subject: [PATCH 25/25] quell linter --- trie/utils/verkle_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trie/utils/verkle_test.go b/trie/utils/verkle_test.go index ea6b733feddb..a5aec55a0660 100644 --- a/trie/utils/verkle_test.go +++ b/trie/utils/verkle_test.go @@ -104,7 +104,7 @@ func TestCompareGetTreeKeyWithEvaluated(t *testing.T) { rand.Read(val[:]) n := uint256.NewInt(0).SetBytes(val[:]) n.Lsh(n, 8) - subindex := byte(val[0]) + subindex := val[0] tk1 := GetTreeKey(addr[:], n, subindex) tk2 := GetTreeKeyWithEvaluatedAddess(addrpoint, n, subindex)