diff --git a/.github/workflows/spec-tests-branch.yml b/.github/workflows/spec-tests-branch.yml index ce739a7800fb..9648f6db699f 100644 --- a/.github/workflows/spec-tests-branch.yml +++ b/.github/workflows/spec-tests-branch.yml @@ -4,7 +4,7 @@ on: push: branches: [master] pull_request: - branches: [master, kaustinen-with-shapella] + branches: [master, kaustinen-with-shapella, jsign-witness-fix] workflow_dispatch: env: @@ -61,17 +61,13 @@ jobs: - name: Clone execution-spec-tests and fill tests run: | - git clone https://github.com/${{ env.EEST_USER }}/execution-spec-tests -b ${{ env.EEST_BRANCH }} + curl -LsSf https://astral.sh/uv/install.sh | sh + git clone https://github.com/${{ env.EEST_USER }}/execution-spec-tests -b ${{ env.EEST_BRANCH }} --depth 1 cd execution-spec-tests - python3 -m venv venv - . venv/bin/activate - pip install --upgrade pip - pip install -e ".[docs,lint,test]" - solc-select use 0.8.24 --always-install if [ "${{ matrix.test-type }}" == "genesis" ]; then - fill --fork Verkle --output=../fixtures-${{ matrix.test-type }} -v -m blockchain_test -n auto + uv run fill --evm-bin="${{ github.workspace }}/bin/evm" --fork Verkle --output=../fixtures-${{ matrix.test-type }} -v -m blockchain_test -n auto else - fill --from Shanghai --until EIP6800Transition --output=../fixtures-${{ matrix.test-type }} -v -m blockchain_test -n auto + uv run fill --evm-bin="${{ github.workspace }}/bin/evm" --from Shanghai --until EIP6800Transition --output=../fixtures-${{ matrix.test-type }} -v -m blockchain_test -n auto fi shell: bash @@ -107,12 +103,8 @@ jobs: - name: Clone execution-spec-tests and consume tests run: | + curl -LsSf https://astral.sh/uv/install.sh | sh git clone https://github.com/${{ env.EEST_USER }}/execution-spec-tests -b ${{ env.EEST_BRANCH }} cd execution-spec-tests - python3 -m venv venv - . venv/bin/activate - pip install --upgrade pip - pip install -e ".[docs,lint,test]" - solc-select use 0.8.24 --always-install - consume direct --input=../fixtures-${{ matrix.test-type }} -n auto + uv run consume direct --evm-bin="${{ github.workspace }}/bin/evm" --input=../fixtures-${{ matrix.test-type }} -n auto shell: bash diff --git a/.github/workflows/stable-spec-tests.yml b/.github/workflows/stable-spec-tests.yml index 7aa64a11d695..0b34773d5b0f 100644 --- a/.github/workflows/stable-spec-tests.yml +++ b/.github/workflows/stable-spec-tests.yml @@ -4,11 +4,11 @@ on: push: branches: [master] pull_request: - branches: [master, kaustinen-with-shapella] + branches: [master, kaustinen-with-shapella, jsign-witness-fix] workflow_dispatch: env: - FIXTURES_TAG: "verkle@v0.0.3" + FIXTURES_TAG: "verkle@v0.0.5" jobs: setup: @@ -71,13 +71,8 @@ jobs: extract: true - name: Clone execution-spec-tests and consume tests run: | - git clone https://github.com/ethereum/execution-spec-tests + curl -LsSf https://astral.sh/uv/install.sh | sh + git clone https://github.com/ethereum/execution-spec-tests -b ${{ env.FIXTURES_TAG }} --depth 1 cd execution-spec-tests - git checkout 250a064ba38cd92cad02691f4f7c2ecbefedd954 - python3 -m venv venv - . venv/bin/activate - pip install --upgrade pip - pip install -e ".[docs,lint,test]" - solc-select use 0.8.24 --always-install - consume direct --input=../fixtures -n auto + uv run consume direct --evm-bin="${{ github.workspace }}/bin/evm" --input=../fixtures -n auto shell: bash diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index a151047eeea1..04bef1b27f2e 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -343,7 +343,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, // Amount is in gwei, turn into wei amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) statedb.AddBalance(w.Address, amount) - statedb.Witness().TouchFullAccount(w.Address[:], true) + statedb.Witness().TouchFullAccount(w.Address[:], true, nil) } if chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) { if err := overlay.OverlayVerkleTransition(statedb, common.Hash{}, chainConfig.OverlayStride); err != nil { @@ -451,6 +451,7 @@ func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prest codeHash := crypto.Keccak256Hash(acc.Code) rawdb.WriteCode(codeWriter, codeHash, acc.Code) } + statedb.Commit(0, false) return statedb } diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 1fe5ffb612ff..741653f5c906 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -358,7 +358,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. state.AddBalance(w.Address, amount) // The returned gas is not charged - state.Witness().TouchFullAccount(w.Address[:], true) + state.Witness().TouchFullAccount(w.Address[:], true, nil) } if chain.Config().IsVerkle(header.Number, header.Time) { diff --git a/core/state/access_witness.go b/core/state/access_witness.go index a881e97717f3..2c05a3f76a78 100644 --- a/core/state/access_witness.go +++ b/core/state/access_witness.go @@ -18,7 +18,6 @@ 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" @@ -30,6 +29,9 @@ import ( // * the second bit is set if the branch has been read type mode byte +// UseGasFn is a function that can be used to charge gas for a given amount. +type UseGasFn func(uint64) bool + const ( AccessWitnessReadFlag = mode(1) AccessWitnessWriteFlag = mode(2) @@ -89,100 +91,69 @@ func (aw *AccessWitness) Copy() *AccessWitness { return naw } -func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { - var gas uint64 +func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool, useGasFn UseGasFn) bool { for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ { - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite) + if _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite, useGasFn); !ok { + return false + } } - return gas + return true } -func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 { - var gas uint64 - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false) - return gas +func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte, useGasFn UseGasFn) bool { + chargedGas, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, useGasFn) + return ok && (chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929)) } -func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 { - var gas uint64 - gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) - gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) - return gas +func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte, useGasFn UseGasFn) bool { + chargedGas1, ok := aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, useGasFn) + if !ok { + return false + } + chargedGas2, ok := aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, useGasFn) + return ok && (chargedGas1+chargedGas2 > 0 || useGasFn(params.WarmStorageReadCostEIP2929)) } // TouchAndChargeContractCreateCheck charges access costs before // a contract creation is initiated. It is just reads, because the // address collision is done before the transfer, and so no write // are guaranteed to happen at this point. -func (aw *AccessWitness) TouchAndChargeContractCreateCheck(addr []byte) uint64 { - var gas uint64 - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false) - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) - return gas +func (aw *AccessWitness) TouchAndChargeContractCreateCheck(addr []byte, useGasFn UseGasFn) bool { + if _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, useGasFn); !ok { + return false + } + _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, useGasFn) + return ok } // TouchAndChargeContractCreateInit charges access costs to initiate // a contract creation. -func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte) uint64 { - var gas uint64 - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true) - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true) - return gas +func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, useGasFn UseGasFn) bool { + if _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, useGasFn); !ok { + return false + } + _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, useGasFn) + return ok } -func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 { +func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) { for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ { - aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey) + aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey, nil) } - - // 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. - // This is the reason why we return 0 instead of `gas`. - // Note that we still have to touch the addresses to make sure the witness is correct. - return 0 } -func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 { - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue) - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false) - - // 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. - // This is the reason why we return 0 instead of `gas`. - // Note that we still have to touch the addresses to make sure the witness is correct. - return 0 +func (aw *AccessWitness) TouchTxTarget(targetAddr []byte, sendsValue bool) { + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, nil) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, nil) } -func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool) uint64 { +func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool, useGasFn UseGasFn, warmCostCharging bool) bool { treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes()) - return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) -} - -func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { - stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, treeIndex, subIndex, isWrite) - - var gas uint64 - if stemRead { - gas += params.WitnessBranchReadCost - } - if selectorRead { - gas += params.WitnessChunkReadCost - } - if stemWrite { - gas += params.WitnessBranchWriteCost - } - if selectorWrite { - gas += params.WitnessChunkWriteCost - } - if selectorFill { - gas += params.WitnessChunkFillCost - } - - return gas + chargedGas, ok := aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, useGasFn) + return ok && (!warmCostCharging || chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929)) } -// touchAddress adds any missing access event to the witness. -func (aw *AccessWitness) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { +func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool, useGasFn UseGasFn) (uint64, bool) { branchKey := newBranchAccessKey(addr, treeIndex) chunkKey := newChunkAccessKey(branchKey, subIndex) @@ -190,11 +161,9 @@ func (aw *AccessWitness) touchAddress(addr []byte, treeIndex uint256.Int, subInd var branchRead, chunkRead bool if _, hasStem := aw.branches[branchKey]; !hasStem { branchRead = true - aw.branches[branchKey] = AccessWitnessReadFlag } if _, hasSelector := aw.chunks[chunkKey]; !hasSelector { chunkRead = true - aw.chunks[chunkKey] = AccessWitnessReadFlag } // Write access. @@ -202,19 +171,52 @@ func (aw *AccessWitness) touchAddress(addr []byte, treeIndex uint256.Int, subInd if isWrite { if (aw.branches[branchKey] & AccessWitnessWriteFlag) == 0 { branchWrite = true - aw.branches[branchKey] |= AccessWitnessWriteFlag } chunkValue := aw.chunks[chunkKey] if (chunkValue & AccessWitnessWriteFlag) == 0 { chunkWrite = true - aw.chunks[chunkKey] |= AccessWitnessWriteFlag } + } - // TODO: charge chunk filling costs if the leaf was previously empty in the state + var gas uint64 + if branchRead { + gas += params.WitnessBranchReadCost + } + if chunkRead { + gas += params.WitnessChunkReadCost + } + if branchWrite { + gas += params.WitnessBranchWriteCost + } + if chunkWrite { + gas += params.WitnessChunkWriteCost + } + if chunkFill { + gas += params.WitnessChunkFillCost + } + + if useGasFn != nil { + if ok := useGasFn(gas); !ok { + return 0, false + } + } + + if branchRead { + aw.branches[branchKey] = AccessWitnessReadFlag + } + if branchWrite { + aw.branches[branchKey] |= AccessWitnessWriteFlag + } + if chunkRead { + aw.chunks[chunkKey] = AccessWitnessReadFlag + } + if chunkWrite { + chunkWrite = true + aw.chunks[chunkKey] |= AccessWitnessWriteFlag } - return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill + return gas, true } type branchAccessKey struct { @@ -242,7 +244,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { } // touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs -func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { +func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool, useGasFn UseGasFn) bool { // note that in the case where the copied code is outside the range of the // contract code but touches the last leaf with contract code in it, // we don't include the last leaf of code in the AccessWitness. The @@ -250,7 +252,7 @@ func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, s // is already in the AccessWitness so a stateless verifier can see that // the code from the last leaf is not needed. if size == 0 || startPC >= codeLen { - return 0 + return true } endPC := startPC + size @@ -261,25 +263,23 @@ func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, s endPC -= 1 // endPC is the last bytecode that will be touched. } - var statelessGasCharged uint64 for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) subIndex := byte((chunkNumber + 128) % 256) - gas := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) - var overflow bool - statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) - if overflow { - panic("overflow when adding gas") + if _, ok := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, useGasFn); !ok { + return false } } - return statelessGasCharged + return true } -func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool) uint64 { - return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite) +func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool, useGasFn UseGasFn, warmCostCharging bool) bool { + chargedGas, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, useGasFn) + return ok && (!warmCostCharging || chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929)) } -func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) uint64 { - return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite) +func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool, useGasFn UseGasFn, warmCostCharging bool) bool { + chargedGas, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, useGasFn) + return ok && (!warmCostCharging || chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929)) } diff --git a/core/state_processor.go b/core/state_processor.go index baf60a5525b7..aa584845355a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -177,7 +177,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo func InsertBlockHashHistoryAtEip2935Fork(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash, chain consensus.ChainHeaderReader) { // Make sure that the historical contract is added to the witness - statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true) + statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true, nil) ancestor := chain.GetHeader(prevHash, prevNumber) for i := prevNumber; i > 0 && i >= prevNumber-params.Eip2935BlockHashHistorySize; i-- { @@ -191,5 +191,5 @@ func ProcessParentBlockHash(statedb *state.StateDB, prevNumber uint64, prevHash var key common.Hash binary.BigEndian.PutUint64(key[24:], ringIndex) statedb.SetState(params.HistoryStorageAddress, key, prevHash) - statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true) + statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true, nil, false) } diff --git a/core/state_transition.go b/core/state_transition.go index 0aa4a369018b..9f35b8d3a94a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -339,19 +339,6 @@ func (st *StateTransition) preCheck() error { return st.buyGas() } -// 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 { - if *gasPool < gas { - *gasPool = 0 - return false - } - - *gasPool -= gas - return true -} - // TransitionDb will transition the state by applying the current message and // returning the evm execution result with following fields. // @@ -406,15 +393,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { targetAddr := msg.To originAddr := msg.From - statelessGasOrigin := st.evm.Accesses.TouchTxOriginAndComputeGas(originAddr.Bytes()) - 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) - } + st.evm.Accesses.TouchTxOriginAndComputeGas(originAddr.Bytes()) if msg.To != nil { - statelessGasDest := st.evm.Accesses.TouchTxExistingAndComputeGas(targetAddr.Bytes(), msg.Value.Sign() != 0) - if !tryConsumeGas(&st.gasRemaining, statelessGasDest) { - return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) + st.evm.Accesses.TouchTxTarget(targetAddr.Bytes(), msg.Value.Sign() != 0) + if !st.state.Exist(*targetAddr) { + st.evm.Accesses.TouchCodeHash(targetAddr.Bytes(), true, nil, false) } // ensure the code size ends up in the access witness @@ -472,7 +456,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true) + st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true, nil) } } diff --git a/core/vm/common.go b/core/vm/common.go index ba75950e370b..226a337acd56 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -92,3 +92,15 @@ func allZero(b []byte) bool { } return true } + +type gasConsumer struct { + availableGas uint64 +} + +func (gc *gasConsumer) consumeGas(gas uint64) bool { + if gc.availableGas < gas { + return false + } + gc.availableGas -= gas + return true +} diff --git a/core/vm/eips.go b/core/vm/eips.go index 1df79f6cffd0..99d884e0ae37 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -307,26 +307,26 @@ func enable6780(jt *JumpTable) { func enable4762(jt *JumpTable) { jt[SSTORE].constantGas = 0 - jt[SSTORE].dynamicGas = gasSStore4762 + jt[SSTORE].dynamicGas = nil jt[SLOAD].constantGas = 0 - jt[SLOAD].dynamicGas = gasSLoad4762 - jt[BALANCE].dynamicGas = gasBalance4762 + jt[SLOAD].dynamicGas = nil + jt[BALANCE].dynamicGas = nil jt[BALANCE].constantGas = 0 jt[EXTCODESIZE].constantGas = 0 - jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762 + jt[EXTCODESIZE].dynamicGas = nil jt[EXTCODEHASH].constantGas = 0 - jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 + jt[EXTCODEHASH].dynamicGas = nil jt[EXTCODECOPY].constantGas = 0 - jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762 - jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopy + jt[SELFDESTRUCT].dynamicGas = nil jt[CREATE].constantGas = params.CreateNGasEip4762 jt[CREATE2].constantGas = params.CreateNGasEip4762 jt[CALL].constantGas = 0 - jt[CALL].dynamicGas = gasCallEIP4762 + jt[CALL].dynamicGas = gasZero jt[CALLCODE].constantGas = 0 - jt[CALLCODE].dynamicGas = gasCallCodeEIP4762 + jt[CALLCODE].dynamicGas = gasZero jt[STATICCALL].constantGas = 0 - jt[STATICCALL].dynamicGas = gasStaticCallEIP4762 + jt[STATICCALL].dynamicGas = gasZero jt[DELEGATECALL].constantGas = 0 - jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP4762 + jt[DELEGATECALL].dynamicGas = gasZero } diff --git a/core/vm/evm.go b/core/vm/evm.go index fed7302b5919..f70f3552024a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -200,14 +200,20 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas debug := evm.Config.Tracer != nil if !evm.StateDB.Exist(addr) { - if !isPrecompile && evm.chainRules.IsEIP4762 { - // add proof of absence to witness - wgas := evm.Accesses.TouchFullAccount(addr.Bytes(), false) - if gas < wgas { + if !isPrecompile && evm.chainRules.IsEIP4762 && value.Sign() != 0 { + gc := gasConsumer{availableGas: gas} + // At this point, the read costs have already been charged, either because this + // is a direct tx call, in which case it's covered by the intrinsic gas, or because + // of a CALL instruction, in which case BASIC_DATA has been added to the access + // list in write mode. If there is enough gas paying for the addition of the code + // hash leaf to the access list, then account creation will proceed unimpaired. + // Thus, only pay for the creation of the code hash leaf here. + ok := evm.Accesses.TouchCodeHash(addr.Bytes(), true, gc.consumeGas, false) + if !ok { evm.StateDB.RevertToSnapshot(snapshot) return nil, 0, ErrOutOfGas } - gas -= wgas + gas = gc.availableGas } if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { @@ -457,11 +463,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - statelessGas := evm.Accesses.TouchAndChargeContractCreateCheck(address.Bytes()) - if statelessGas > gas { + gc := gasConsumer{availableGas: gas} + if !evm.Accesses.TouchAndChargeContractCreateCheck(address.Bytes(), gc.consumeGas) { return nil, common.Address{}, 0, ErrOutOfGas } - gas = gas - statelessGas + gas = gc.availableGas } // 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 @@ -473,6 +479,16 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { return nil, common.Address{}, 0, ErrContractAddressCollision } + + // Charge the contract creation init gas in verkle mode + if evm.chainRules.IsEIP4762 { + gc := gasConsumer{availableGas: gas} + if !evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), gc.consumeGas) { + return nil, common.Address{}, 0, ErrOutOfGas + } + gas = gc.availableGas + } + // Create a new account on the state snapshot := evm.StateDB.Snapshot() evm.StateDB.CreateAccount(address) @@ -480,14 +496,6 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.StateDB.SetNonce(address, 1) } - // Charge the contract creation init gas in verkle mode - if evm.chainRules.IsEIP4762 { - statelessGas := evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes()) - if statelessGas > gas { - return nil, common.Address{}, 0, ErrOutOfGas - } - gas = gas - statelessGas - } evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) // Initialise a new contract and set the code that is to be used by the EVM. @@ -496,8 +504,6 @@ 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 - if evm.Config.Tracer != nil { if evm.depth == 0 { evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) @@ -529,7 +535,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, err = ErrCodeStoreOutOfGas } } else { - if err == nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) { + if len(ret) > 0 && !evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true, contract.UseGas) { err = ErrCodeStoreOutOfGas } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 7fcfd0472e3a..6fbc6e7dd299 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -403,25 +403,6 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return 0, ErrGasUintOverflow } - if evm.chainRules.IsEIP4762 { - // If value is transferred, it is charged before 1/64th - // is subtracted from the available gas pool. - if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:])) - if overflow { - return 0, ErrGasUintOverflow - } - } - } - - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) - if err != nil { - return 0, err - } - if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow - } - return gas, nil } @@ -440,29 +421,19 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) - if err != nil { - return 0, err - } - if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow - } + return gas, nil } +func gasZero(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return 0, nil +} + func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) - if err != nil { - return 0, err - } - var overflow bool - if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow - } return gas, nil } @@ -471,14 +442,6 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if err != nil { return 0, err } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) - if err != nil { - return 0, err - } - var overflow bool - if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow - } return gas, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 653a9f1eead4..412093b1bb04 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -20,7 +20,7 @@ import ( "encoding/binary" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" @@ -262,6 +262,11 @@ func opAddress(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) + if interpreter.evm.chainRules.IsVerkle { + if !interpreter.evm.Accesses.TouchBasicData(address[:], false, scope.Contract.UseGas, true) { + return nil, ErrOutOfGas + } + } slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) return nil, nil } @@ -346,6 +351,18 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) slot := scope.Stack.peek() address := slot.Bytes20() cs := uint64(interpreter.evm.StateDB.GetCodeSize(address)) + if interpreter.evm.chainRules.IsVerkle { + isSystemContract := interpreter.evm.isSystemContract(address) + if _, isPrecompile := interpreter.evm.precompile(address); isPrecompile || isSystemContract { + if !scope.Contract.UseGas(params.WarmStorageReadCostEIP2929) { + return nil, ErrOutOfGas + } + } else { + if !interpreter.evm.Accesses.TouchBasicData(address[:], false, scope.Contract.UseGas, true) { + return nil, ErrOutOfGas + } + } + } slot.SetUint64(cs) return nil, nil } @@ -371,8 +388,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ codeAddr := scope.Contract.CodeAddr paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment { - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false) - if !scope.Contract.UseGas(statelessGas) { + if !interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas } @@ -389,6 +405,21 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) codeOffset = stack.pop() length = stack.pop() ) + + if interpreter.evm.chainRules.IsVerkle { + addr := common.Address(a.Bytes20()) + isSystemContract := interpreter.evm.isSystemContract(addr) + if _, isPrecompile := interpreter.evm.precompile(addr); isPrecompile || isSystemContract { + if !scope.Contract.UseGas(params.WarmStorageReadCostEIP2929) { + return nil, ErrOutOfGas + } + } else { + if !interpreter.evm.Accesses.TouchBasicData(addr[:], false, scope.Contract.UseGas, true) { + return nil, ErrOutOfGas + } + } + } + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { uint64CodeOffset = 0xffffffffffffffff @@ -401,8 +432,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) - if !scope.Contract.UseGas(statelessGas) { + if !interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, scope.Contract.UseGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas } @@ -444,6 +474,17 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) + if interpreter.evm.chainRules.IsVerkle { + if _, isPrecompile := interpreter.evm.precompile(address); isPrecompile || interpreter.evm.isSystemContract(address) { + if !scope.Contract.UseGas(params.WarmStorageReadCostEIP2929) { + return nil, ErrOutOfGas + } + } else { + if !interpreter.evm.Accesses.TouchCodeHash(address[:], false, scope.Contract.UseGas, true) { + return nil, ErrOutOfGas + } + } + } if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else { @@ -458,14 +499,6 @@ func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ return nil, nil } -func getBlockHashFromContract(number uint64, statedb StateDB, witness *state.AccessWitness) (common.Hash, uint64) { - ringIndex := number % params.Eip2935BlockHashHistorySize - var pnum common.Hash - binary.BigEndian.PutUint64(pnum[24:], ringIndex) - statelessGas := witness.TouchSlotAndChargeGas(params.HistoryStorageAddress[:], pnum, false) - return statedb.GetState(params.HistoryStorageAddress, pnum), statelessGas -} - func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { num := scope.Stack.peek() num64, overflow := num.Uint64WithOverflow() @@ -486,12 +519,13 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( if num64 >= lower && num64 < upper { // if Verkle is active, read it from the history contract (EIP 2935). if evm.chainRules.IsVerkle { - blockHash, statelessGas := getBlockHashFromContract(num64, evm.StateDB, evm.Accesses) - if interpreter.evm.chainRules.IsEIP4762 { - if !scope.Contract.UseGas(statelessGas) { - return nil, ErrExecutionReverted - } + ringIndex := num64 % params.Eip2935BlockHashHistorySize + var pnum common.Hash + binary.BigEndian.PutUint64(pnum[24:], ringIndex) + if !evm.Accesses.TouchSlotAndChargeGas(params.HistoryStorageAddress[:], pnum, false, scope.Contract.UseGas, false) { + return nil, ErrOutOfGas } + blockHash := evm.StateDB.GetState(params.HistoryStorageAddress, pnum) num.SetBytes(blockHash.Bytes()) } else { num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) @@ -563,8 +597,12 @@ func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { loc := scope.Stack.peek() hash := common.Hash(loc.Bytes32()) + if interpreter.evm.chainRules.IsVerkle { + if !interpreter.evm.Accesses.TouchSlotAndChargeGas(scope.Contract.Address().Bytes(), loc.Bytes32(), false, scope.Contract.UseGas, true) { + return nil, ErrOutOfGas + } + } val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) - loc.SetBytes(val.Bytes()) return nil, nil } @@ -575,6 +613,12 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } loc := scope.Stack.pop() val := scope.Stack.pop() + if interpreter.evm.chainRules.IsVerkle { + if !interpreter.evm.Accesses.TouchSlotAndChargeGas(scope.Contract.Address().Bytes(), loc.Bytes32(), true, scope.Contract.UseGas, true) { + return nil, ErrOutOfGas + } + } + interpreter.evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) return nil, nil } @@ -585,7 +629,7 @@ func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } pos := scope.Stack.pop() if !scope.Contract.validJumpdest(&pos) { - if !scope.Contract.UseGas(interpreter.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(scope.Contract.CodeAddr[:], pos.Uint64(), 1, uint64(len(scope.Contract.Code)), false)) { + if !interpreter.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(scope.Contract.CodeAddr[:], pos.Uint64(), 1, uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { return nil, ErrOutOfGas } return nil, ErrInvalidJump @@ -601,7 +645,7 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by pos, cond := scope.Stack.pop(), scope.Stack.pop() if !cond.IsZero() { if !scope.Contract.validJumpdest(&pos) { - if !scope.Contract.UseGas(interpreter.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(scope.Contract.CodeAddr[:], pos.Uint64(), 1, uint64(len(scope.Contract.Code)), false)) { + if !interpreter.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(scope.Contract.CodeAddr[:], pos.Uint64(), 1, uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { return nil, ErrOutOfGas } return nil, ErrInvalidJump @@ -720,7 +764,84 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] return nil, nil } +func chargeCallVariantEIP4762(evm *EVM, scope *ScopeContext) bool { + target := common.Address(scope.Stack.Back(1).Bytes20()) + if _, isPrecompile := evm.precompile(target); isPrecompile || evm.isSystemContract(target) { + return scope.Contract.UseGas(params.WarmStorageReadCostEIP2929) + } + // The charging for the value transfer is done BEFORE subtracting + // the 1/64th gas, as this is considered part of the CALL instruction. + // (so before we get to this point) + // But the message call is part of the subcall, for which only 63/64th + // of the gas should be available. + if !evm.Accesses.TouchAndChargeMessageCall(target.Bytes(), scope.Contract.UseGas) { + return false + } + return true + +} + +func getMemSize(operation *operation, stack *Stack) (uint64, error) { + // All ops with a dynamic memory usage also has a dynamic gas cost. + var memorySize uint64 + // calculate the new memory size and expand the memory to fit + // the operation + // Memory check needs to be done prior to evaluating the dynamic gas portion, + // to detect calculation overflows + if operation.memorySize != nil { + memSize, overflow := operation.memorySize(stack) + if overflow { + return 0, ErrGasUintOverflow + } + // memory is expanded in words of 32 bytes. Gas + // is also calculated in words. + if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { + return 0, ErrGasUintOverflow + } + } + return memorySize, nil +} + func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + transfersValue := !scope.Stack.Back(2).IsZero() + if interpreter.evm.chainRules.IsEIP4762 { + address := common.Address(scope.Stack.Back(1).Bytes20()) + + // If value is transferred, it is charged before 1/64th + // is subtracted from the available gas pool. + if transfersValue { + if !interpreter.evm.Accesses.TouchAndChargeValueTransfer(scope.Contract.Address().Bytes()[:], address.Bytes()[:], scope.Contract.UseGas) { + return nil, ErrOutOfGas + } + } + } + + if interpreter.evm.chainRules.IsEIP4762 && !transfersValue && !chargeCallVariantEIP4762(interpreter.evm, scope) { + return nil, ErrOutOfGas + } + if interpreter.evm.chainRules.IsEIP4762 { + memSize, err := getMemSize(interpreter.table[CALL], scope.Stack) + if err != nil { + return nil, err + } + dynamicCost, err := gasCall(interpreter.evm, scope.Contract, scope.Stack, scope.Memory, memSize) + if err != nil || !scope.Contract.UseGas(dynamicCost) { + return nil, ErrOutOfGas + } + if memSize > 0 { + scope.Memory.Resize(memSize) + } + } + + var err error + interpreter.evm.callGasTemp, err = callGas(interpreter.evm.chainRules.IsEIP150, scope.Contract.Gas, 0, scope.Stack.Back(0)) + if err != nil { + return nil, err + } + if !scope.Contract.UseGas(interpreter.evm.callGasTemp) { + return nil, ErrOutOfGas + } + stack := scope.Stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. // We can use this as a temporary value @@ -762,6 +883,31 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.evm.chainRules.IsEIP4762 && !chargeCallVariantEIP4762(interpreter.evm, scope) { + return nil, ErrOutOfGas + } + if interpreter.evm.chainRules.IsEIP4762 { + memSize, err := getMemSize(interpreter.table[CALLCODE], scope.Stack) + if err != nil { + return nil, err + } + dynamicCost, err := gasCallCode(interpreter.evm, scope.Contract, scope.Stack, scope.Memory, memSize) + if err != nil || !scope.Contract.UseGas(dynamicCost) { + return nil, ErrOutOfGas + } + if memSize > 0 { + scope.Memory.Resize(memSize) + } + } + var err error + interpreter.evm.callGasTemp, err = callGas(interpreter.evm.chainRules.IsEIP150, scope.Contract.Gas, 0, scope.Stack.Back(0)) + if err != nil { + return nil, err + } + if !scope.Contract.UseGas(interpreter.evm.callGasTemp) { + return nil, ErrOutOfGas + } + // Pop gas. The actual gas is in interpreter.evm.callGasTemp. stack := scope.Stack // We use it as a temporary value @@ -797,6 +943,31 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.evm.chainRules.IsEIP4762 && !chargeCallVariantEIP4762(interpreter.evm, scope) { + return nil, ErrOutOfGas + } + if interpreter.evm.chainRules.IsEIP4762 { + memSize, err := getMemSize(interpreter.table[DELEGATECALL], scope.Stack) + if err != nil { + return nil, err + } + dynamicCost, err := gasDelegateCall(interpreter.evm, scope.Contract, scope.Stack, scope.Memory, memSize) + if err != nil || !scope.Contract.UseGas(dynamicCost) { + return nil, ErrOutOfGas + } + if memSize > 0 { + scope.Memory.Resize(memSize) + } + } + var err error + interpreter.evm.callGasTemp, err = callGas(interpreter.evm.chainRules.IsEIP150, scope.Contract.Gas, 0, scope.Stack.Back(0)) + if err != nil { + return nil, err + } + if !scope.Contract.UseGas(interpreter.evm.callGasTemp) { + return nil, ErrOutOfGas + } + stack := scope.Stack // Pop gas. The actual gas is in interpreter.evm.callGasTemp. // We use it as a temporary value @@ -825,6 +996,32 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.evm.chainRules.IsEIP4762 && !chargeCallVariantEIP4762(interpreter.evm, scope) { + return nil, ErrOutOfGas + } + + if interpreter.evm.chainRules.IsEIP4762 { + memSize, err := getMemSize(interpreter.table[STATICCALL], scope.Stack) + if err != nil { + return nil, err + } + dynamicCost, err := gasStaticCall(interpreter.evm, scope.Contract, scope.Stack, scope.Memory, memSize) + if err != nil || !scope.Contract.UseGas(dynamicCost) { + return nil, ErrOutOfGas + } + if memSize > 0 { + scope.Memory.Resize(memSize) + } + } + var err error + interpreter.evm.callGasTemp, err = callGas(interpreter.evm.chainRules.IsEIP150, scope.Contract.Gas, 0, scope.Stack.Back(0)) + if err != nil { + return nil, err + } + if !scope.Contract.UseGas(interpreter.evm.callGasTemp) { + return nil, ErrOutOfGas + } + // Pop gas. The actual gas is in interpreter.evm.callGasTemp. stack := scope.Stack // We use it as a temporary value @@ -891,6 +1088,43 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.evm.chainRules.IsVerkle { + beneficiaryAddr := common.Address(scope.Stack.peek().Bytes20()) + contractAddr := scope.Contract.Address() + + if !interpreter.evm.Accesses.TouchBasicData(contractAddr[:], false, scope.Contract.UseGas, false) { + return nil, ErrOutOfGas + } + + balanceIsZero := interpreter.evm.StateDB.GetBalance(contractAddr).Sign() == 0 + _, isPrecompile := interpreter.evm.precompile(beneficiaryAddr) + isSystemContract := interpreter.evm.isSystemContract(beneficiaryAddr) + if (!isPrecompile && !isSystemContract) || !balanceIsZero { + if contractAddr != beneficiaryAddr { + if !interpreter.evm.Accesses.TouchBasicData(beneficiaryAddr[:], false, scope.Contract.UseGas, false) { + return nil, ErrOutOfGas + } + } + // Charge write costs if it transfers value + if !balanceIsZero { + if !interpreter.evm.Accesses.TouchBasicData(contractAddr[:], true, scope.Contract.UseGas, false) { + return nil, ErrOutOfGas + } + if contractAddr != beneficiaryAddr { + if interpreter.evm.StateDB.Exist(beneficiaryAddr) { + if !interpreter.evm.Accesses.TouchBasicData(beneficiaryAddr[:], true, scope.Contract.UseGas, false) { + return nil, ErrOutOfGas + } + } else { + if !interpreter.evm.Accesses.TouchFullAccount(beneficiaryAddr[:], true, scope.Contract.UseGas) { + return nil, ErrOutOfGas + } + } + } + } + } + } + if interpreter.readOnly { return nil, ErrWriteProtection } @@ -950,8 +1184,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. codeAddr := scope.Contract.CodeAddr - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) - if !scope.Contract.UseGas(statelessGas) { + if !interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], *pc, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas } @@ -979,8 +1212,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsVerkle { codeAddr := scope.Contract.CodeAddr - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) - if !scope.Contract.UseGas(statelessGas) { + if !interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.UseGas) { scope.Contract.Gas = 0 return nil, ErrOutOfGas } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 9a1897ec7e83..fed4067a07f9 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. codeAddr := contract.CodeAddr - if !contract.UseGas(in.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], pc, 1, uint64(len(contract.Code)), false)) { + if !in.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(codeAddr[:], pc, 1, uint64(len(contract.Code)), false, contract.UseGas) { return nil, ErrOutOfGas } } @@ -233,7 +233,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) logged = true } - if memorySize > 0 { + // TODO(hack): remove if conditions + if (!in.evm.chainRules.IsEIP4762 || (op != CALL && op != CALLCODE && op != DELEGATECALL && op != STATICCALL)) && memorySize > 0 { mem.Resize(memorySize) } } else if debug { diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go deleted file mode 100644 index 594c84236ba8..000000000000 --- a/core/vm/operations_verkle.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2024 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package vm - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/params" -) - -func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) - 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.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) - 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() - gas := evm.Accesses.TouchBasicData(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() - if _, isPrecompile := evm.precompile(address); isPrecompile { - return params.WarmStorageReadCostEIP2929, nil - } - wgas := evm.Accesses.TouchBasicData(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 || evm.isSystemContract(address) { - return params.WarmStorageReadCostEIP2929, nil - } - codehashgas := evm.Accesses.TouchCodeHash(address[:], false) - if codehashgas == 0 { - codehashgas = params.WarmStorageReadCostEIP2929 - } - return codehashgas, nil -} - -func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) - if err != nil { - return 0, err - } - target := common.Address(stack.Back(1).Bytes20()) - if _, isPrecompile := evm.precompile(target); isPrecompile { - var overflow bool - if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow { - return 0, ErrGasUintOverflow - } - return gas, nil - } - // The charging for the value transfer is done BEFORE subtracting - // the 1/64th gas, as this is considered part of the CALL instruction. - // (so before we get to this point) - // But the message call is part of the subcall, for which only 63/64th - // of the gas should be available. - wgas := evm.Accesses.TouchAndChargeMessageCall(target.Bytes()) - if wgas == 0 { - wgas = params.WarmStorageReadCostEIP2929 - } - return wgas + gas, nil - } -} - -var ( - gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall) - gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode) - gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall) - gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall) -) - -func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - beneficiaryAddr := common.Address(stack.peek().Bytes20()) - contractAddr := contract.Address() - - statelessGas := evm.Accesses.TouchBasicData(contractAddr[:], false) - balanceIsZero := evm.StateDB.GetBalance(contractAddr).Sign() == 0 - - if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile && balanceIsZero { - return statelessGas, nil - } - - if contractAddr != beneficiaryAddr { - statelessGas += evm.Accesses.TouchBasicData(beneficiaryAddr[:], false) - } - // Charge write costs if it transfers value - if !balanceIsZero { - statelessGas += evm.Accesses.TouchBasicData(contractAddr[:], true) - if contractAddr != beneficiaryAddr { - if evm.StateDB.Exist(beneficiaryAddr) { - statelessGas += evm.Accesses.TouchBasicData(beneficiaryAddr[:], true) - } else { - statelessGas += evm.Accesses.TouchFullAccount(beneficiaryAddr[:], true) - } - } - } - return statelessGas, nil -} - -func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // memory expansion first (dynamic part of pre-2929 implementation) - gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) - if err != nil { - return 0, err - } - addr := common.Address(stack.peek().Bytes20()) - - if _, isPrecompile := evm.precompile(addr); isPrecompile { - var overflow bool - if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow { - return 0, ErrGasUintOverflow - } - return gas, nil - } - wgas := evm.Accesses.TouchBasicData(addr[:], false) - if wgas == 0 { - wgas = params.WarmStorageReadCostEIP2929 - } - var overflow bool - if gas, overflow = math.SafeAdd(gas, wgas); overflow { - return 0, ErrGasUintOverflow - } - return gas, nil -}