1
0
Fork 0
forked from forks/go-ethereum

core/state, core/vm: update stateless gas costs to follow the verkle-gen-7 testnet (#31014)

Adding values to the witness introduces a new class of issues for
computing gas: if there is not enough gas to cover adding an item to the
witness, then the item should not be added to the witness.

The problem happens when several items are added together, and that
process runs out of gas. The witness gas computation needs a way to
signal that not enough gas was provided. These values can not be
hardcoded, however, as they are context dependent, i.e. two calls to the
same function with the same parameters can give two different results.

The approach is to return both the gas that was actually consumed, and
the gas that was necessary. If the values don't match, then a witness
update OOG'd. The caller should then charge the `consumed` value
(remaining gas will be 0) and error out.

Why not return a boolean instead of the wanted value? Because when
several items are touched, we want to distinguish which item lacked gas.

---------

Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
This commit is contained in:
Guillaume Ballet 2025-05-15 14:43:52 +02:00 committed by GitHub
parent 228803c1a2
commit af9a3a1a03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 276 additions and 203 deletions

View file

@ -18,6 +18,7 @@ package state
import ( import (
"maps" "maps"
gomath "math"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
@ -92,97 +93,94 @@ func (ae *AccessEvents) Copy() *AccessEvents {
// AddAccount returns the gas to be charged for each of the currently cold // AddAccount returns the gas to be charged for each of the currently cold
// member fields of an account. // member fields of an account.
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 {
var gas uint64 var gas uint64 // accumulate the consumed gas
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite) consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite) if consumed < expected {
return expected
}
gas += consumed
consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-consumed)
if consumed < expected {
return expected + gas
}
gas += expected
return gas return gas
} }
// MessageCallGas returns the gas to be charged for each of the currently // MessageCallGas returns the gas to be charged for each of the currently
// cold member fields of an account, that need to be touched when making a message // cold member fields of an account, that need to be touched when making a message
// call to that account. // call to that account.
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 { func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) uint64 {
var gas uint64 _, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false) if expected == 0 {
return gas expected = params.WarmStorageReadCostEIP2929
}
return expected
} }
// ValueTransferGas returns the gas to be charged for each of the currently // ValueTransferGas returns the gas to be charged for each of the currently
// cold balance member fields of the caller and the callee accounts. // cold balance member fields of the caller and the callee accounts.
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 { func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) uint64 {
var gas uint64 _, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) if expected1 > availableGas {
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) return expected1
return gas }
_, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-expected1)
if expected1+expected2 == 0 {
return params.WarmStorageReadCostEIP2929
}
return expected1 + expected2
} }
// ContractCreatePreCheckGas charges access costs before // ContractCreatePreCheckGas charges access costs before
// a contract creation is initiated. It is just reads, because the // a contract creation is initiated. It is just reads, because the
// address collision is done before the transfer, and so no write // address collision is done before the transfer, and so no write
// are guaranteed to happen at this point. // are guaranteed to happen at this point.
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address) uint64 { func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) uint64 {
var gas uint64 consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false) _, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-consumed)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) return expected1 + expected2
return gas
} }
// ContractCreateInitGas returns the access gas costs for the initialization of // ContractCreateInitGas returns the access gas costs for the initialization of
// a contract creation. // a contract creation.
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address) uint64 { func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, uint64) {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true) consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true) gas += consumed
return gas consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-consumed)
gas += consumed
return gas, expected1 + expected2
} }
// AddTxOrigin adds the member fields of the sender account to the access event list, // AddTxOrigin adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) { func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, gomath.MaxUint64)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, gomath.MaxUint64)
} }
// AddTxDestination adds the member fields of the sender account to the access event list, // AddTxDestination adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) { func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesntExist bool) {
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, gomath.MaxUint64)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist, gomath.MaxUint64)
} }
// SlotGas returns the amount of gas to be charged for a cold storage access. // SlotGas returns the amount of gas to be charged for a cold storage access.
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 { func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) _, expected := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
expected = params.WarmStorageReadCostEIP2929
}
return expected
} }
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold // touchAddressAndChargeGas adds any missing access event to the access event list, and returns the
// access cost to be charged, if need be. // consumed and required gas.
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) {
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.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
}
// touchAddress adds any missing access event to the access event list.
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) {
branchKey := newBranchAccessKey(addr, treeIndex) branchKey := newBranchAccessKey(addr, treeIndex)
chunkKey := newChunkAccessKey(branchKey, subIndex) chunkKey := newChunkAccessKey(branchKey, subIndex)
@ -190,11 +188,9 @@ func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int,
var branchRead, chunkRead bool var branchRead, chunkRead bool
if _, hasStem := ae.branches[branchKey]; !hasStem { if _, hasStem := ae.branches[branchKey]; !hasStem {
branchRead = true branchRead = true
ae.branches[branchKey] = AccessWitnessReadFlag
} }
if _, hasSelector := ae.chunks[chunkKey]; !hasSelector { if _, hasSelector := ae.chunks[chunkKey]; !hasSelector {
chunkRead = true chunkRead = true
ae.chunks[chunkKey] = AccessWitnessReadFlag
} }
// Write access. // Write access.
@ -202,17 +198,51 @@ func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int,
if isWrite { if isWrite {
if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 { if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
branchWrite = true branchWrite = true
ae.branches[branchKey] |= AccessWitnessWriteFlag
} }
chunkValue := ae.chunks[chunkKey] chunkValue := ae.chunks[chunkKey]
if (chunkValue & AccessWitnessWriteFlag) == 0 { if (chunkValue & AccessWitnessWriteFlag) == 0 {
chunkWrite = true chunkWrite = true
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
} }
// TODO: charge chunk filling costs if the leaf was previously empty in the state
} }
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
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 availableGas < gas {
// consumed != expected
return availableGas, gas
}
if branchRead {
ae.branches[branchKey] = AccessWitnessReadFlag
}
if branchWrite {
ae.branches[branchKey] |= AccessWitnessWriteFlag
}
if chunkRead {
ae.chunks[chunkKey] = AccessWitnessReadFlag
}
if chunkWrite {
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
}
// consumed == expected
return gas, gas
} }
type branchAccessKey struct { type branchAccessKey struct {
@ -240,7 +270,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
} }
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs // CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, uint64) {
// note that in the case where the copied code is outside the range of the // 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, // contract code but touches the last leaf with contract code in it,
// we don't include the last leaf of code in the AccessWitness. The // we don't include the last leaf of code in the AccessWitness. The
@ -248,7 +278,7 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
// is already in the AccessWitness so a stateless verifier can see that // is already in the AccessWitness so a stateless verifier can see that
// the code from the last leaf is not needed. // the code from the last leaf is not needed.
if (codeLen == 0 && size == 0) || startPC > codeLen { if (codeLen == 0 && size == 0) || startPC > codeLen {
return 0 return 0, 0
} }
endPC := startPC + size endPC := startPC + size
@ -263,22 +293,34 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
subIndex := byte((chunkNumber + 128) % 256) subIndex := byte((chunkNumber + 128) % 256)
gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) consumed, expected := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
// did we OOG ?
if expected > consumed {
return statelessGasCharged + consumed, statelessGasCharged + expected
}
var overflow bool var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed)
if overflow { if overflow {
panic("overflow when adding gas") panic("overflow when adding gas")
} }
availableGas -= consumed
} }
return statelessGasCharged return statelessGasCharged, statelessGasCharged
} }
// BasicDataGas adds the account's basic data to the accessed data, and returns the // BasicDataGas adds the account's basic data to the accessed data, and returns the
// amount of gas that it costs. // amount of gas that it costs.
// Note that an access in write mode implies an access in read mode, whereas an // Note that an access in write mode implies an access in read mode, whereas an
// access in read mode does not imply an access in write mode. // access in read mode does not imply an access in write mode.
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite) _, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
expected = params.WarmStorageReadCostEIP2929
}
return expected
} }
// CodeHashGas adds the account's code hash to the accessed data, and returns the // CodeHashGas adds the account's code hash to the accessed data, and returns the
@ -286,6 +328,13 @@ func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 {
// in write mode. If false, the charged gas corresponds to an access in read mode. // in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in // Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode. // read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite) _, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
expected = params.WarmStorageReadCostEIP2929
}
return expected
} }

View file

@ -17,6 +17,7 @@
package state package state
import ( import (
"math"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -40,50 +41,50 @@ func TestAccountHeaderGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024)) ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost // Check cold read cost
gas := ae.BasicDataGas(testAddr, false) gas := ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.BasicDataGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check cold read costs in the same group no longer incur the branch read cost // Check cold read costs in the same group no longer incur the branch read cost
gas = ae.CodeHashGas(testAddr, false) gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
} }
// Check cold write cost // Check cold write cost
gas = ae.BasicDataGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm write cost // Check warm write cost
gas = ae.BasicDataGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check a write without a read charges both read and write costs // Check a write without a read charges both read and write costs
gas = ae.BasicDataGas(testAddr2, true) gas = ae.BasicDataGas(testAddr2, true, math.MaxUint64, false)
if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check that a write followed by a read charges nothing // Check that a write followed by a read charges nothing
gas = ae.BasicDataGas(testAddr2, false) gas = ae.BasicDataGas(testAddr2, false, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check that reading a slot from the account header only charges the // Check that reading a slot from the account header only charges the
// chunk read cost. // chunk read cost.
gas = ae.SlotGas(testAddr, common.Hash{}, false) gas = ae.SlotGas(testAddr, common.Hash{}, false, math.MaxUint64, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
} }
@ -100,13 +101,13 @@ func TestContractCreateInitGas(t *testing.T) {
} }
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.ContractCreateInitGas(testAddr) gas, _ := ae.ContractCreateInitGas(testAddr, math.MaxUint64)
if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + 2*params.WitnessChunkWriteCost + 2*params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + 2*params.WitnessChunkWriteCost + 2*params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.ContractCreateInitGas(testAddr) gas, _ = ae.ContractCreateInitGas(testAddr, math.MaxUint64)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
@ -118,24 +119,24 @@ func TestMessageCallGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024)) ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.MessageCallGas(testAddr) gas := ae.MessageCallGas(testAddr, math.MaxUint64)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check that reading the basic data and code hash of the same account does not incur the branch read cost // Check that reading the basic data and code hash of the same account does not incur the branch read cost
gas = ae.BasicDataGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
gas = ae.CodeHashGas(testAddr, false) gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check warm read cost // Check warm read cost
gas = ae.MessageCallGas(testAddr) gas = ae.MessageCallGas(testAddr, math.MaxUint64)
if gas != 0 { if gas != params.WarmStorageReadCostEIP2929 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WarmStorageReadCostEIP2929)
} }
} }

View file

@ -457,7 +457,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
st.evm.AccessEvents.AddTxOrigin(msg.From) st.evm.AccessEvents.AddTxOrigin(msg.From)
if targetAddr := msg.To; targetAddr != nil { if targetAddr := msg.To; targetAddr != nil {
st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0) st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0, !st.state.Exist(*targetAddr))
} }
} }
@ -552,7 +552,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// add the coinbase to the witness iff the fee is greater than 0 // add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 { if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true) st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
} }
} }

View file

@ -139,7 +139,7 @@ var PrecompiledContractsPrague = PrecompiledContracts{
var PrecompiledContractsBLS = PrecompiledContractsPrague var PrecompiledContractsBLS = PrecompiledContractsPrague
var PrecompiledContractsVerkle = PrecompiledContractsPrague var PrecompiledContractsVerkle = PrecompiledContractsBerlin
var ( var (
PrecompiledAddressesPrague []common.Address PrecompiledAddressesPrague []common.Address

View file

@ -339,12 +339,10 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC
addr := common.Address(a.Bytes20()) addr := common.Address(a.Bytes20())
code := interpreter.evm.StateDB.GetCode(addr) code := interpreter.evm.StateDB.GetCode(addr)
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
if !scope.Contract.IsSystemCall { consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas)
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false) scope.Contract.UseGas(consumed, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { if consumed < wanted {
scope.Contract.Gas = 0 return nil, ErrOutOfGas
return nil, ErrOutOfGas
}
} }
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
@ -367,9 +365,9 @@ func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
// touch next chunk if PUSH1 is at the boundary. if so, *pc has // touch next chunk if PUSH1 is at the boundary. if so, *pc has
// advanced past this boundary. // advanced past this boundary.
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.UseGas(wanted, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified)
scope.Contract.Gas = 0 if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
} }
@ -395,9 +393,9 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall { if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.UseGas(consumed, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified)
scope.Contract.Gas = 0 if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
} }

View file

@ -206,8 +206,14 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) { if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
// add proof of absence to witness // Add proof of absence to witness
wgas := evm.AccessEvents.AddAccount(addr, false) // 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.
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas, false)
if gas < wgas { if gas < wgas {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
return nil, 0, ErrOutOfGas return nil, 0, ErrOutOfGas
@ -433,7 +439,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
// Charge the contract creation init gas in verkle mode // Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 { if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address) statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas)
if statelessGas > gas { if statelessGas > gas {
return nil, common.Address{}, 0, ErrOutOfGas return nil, common.Address{}, 0, ErrOutOfGas
} }
@ -481,14 +487,14 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
} }
// Charge the contract creation init gas in verkle mode // Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 { if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreateInitGas(address) consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas)
if statelessGas > gas { if consumed < wanted {
return nil, common.Address{}, 0, ErrOutOfGas return nil, common.Address{}, 0, ErrOutOfGas
} }
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractInit) evm.Config.Tracer.OnGasChange(gas, gas-consumed, tracing.GasChangeWitnessContractInit)
} }
gas = gas - statelessGas gas = gas - consumed
} }
evm.Context.Transfer(evm.StateDB, caller, address, value) evm.Context.Transfer(evm.StateDB, caller, address, value)
@ -535,7 +541,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
} else { } else {
if len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas)
contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if len(ret) > 0 && (consumed < wanted) {
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
} }

View file

@ -394,14 +394,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsEIP4762 && !contract.IsSystemCall {
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err
@ -428,16 +421,6 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsEIP4762 && !contract.IsSystemCall {
address := common.Address(stack.Back(1).Bytes20())
transfersValue := !stack.Back(2).IsZero()
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err

View file

@ -237,7 +237,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// if the PC ends up in a new "chunk" of verkleized code, charge the // if the PC ends up in a new "chunk" of verkleized code, charge the
// associated costs. // associated costs.
contractAddr := contract.Address() contractAddr := contract.Address()
contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false) consumed, wanted := in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas)
contract.UseGas(consumed, in.evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if consumed < wanted {
return nil, ErrOutOfGas
}
} }
// Get the operation from the jump table and validate the stack to ensure there are // Get the operation from the jump table and validate the stack to ensure there are

View file

@ -87,7 +87,7 @@ func validate(jt JumpTable) JumpTable {
} }
func newVerkleInstructionSet() JumpTable { func newVerkleInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet() instructionSet := newShanghaiInstructionSet()
enable4762(&instructionSet) enable4762(&instructionSet)
return validate(instructionSet) return validate(instructionSet)
} }

View file

@ -25,31 +25,16 @@ import (
) )
func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true) return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false) return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if contract.IsSystemCall {
return 0, nil
}
address := stack.peek().Bytes20() address := stack.peek().Bytes20()
gas := evm.AccessEvents.BasicDataGas(address, false) return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
@ -57,56 +42,69 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if _, isPrecompile := evm.precompile(address); isPrecompile { if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil return 0, nil
} }
if contract.IsSystemCall { return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil
return 0, nil
}
gas := evm.AccessEvents.BasicDataGas(address, false)
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if contract.IsSystemCall {
return 0, nil
}
address := stack.peek().Bytes20() address := stack.peek().Bytes20()
if _, isPrecompile := evm.precompile(address); isPrecompile { if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil return 0, nil
} }
gas := evm.AccessEvents.CodeHashGas(address, false) return evm.AccessEvents.CodeHashGas(address, false, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := oldCalculator(evm, contract, stack, mem, memorySize) var (
if err != nil { target = common.Address(stack.Back(1).Bytes20())
return 0, err witnessGas uint64
} _, isPrecompile = evm.precompile(target)
if contract.IsSystemCall { isSystemContract = target == params.HistoryStorageAddress
return gas, nil )
}
if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { // If value is transferred, it is charged before 1/64th
return gas, nil // is subtracted from the available gas pool.
} if withTransferCosts && !stack.Back(2).IsZero() {
witnessGas := evm.AccessEvents.MessageCallGas(contract.Address()) wantedValueTransferWitnessGas := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas)
if witnessGas == 0 { if wantedValueTransferWitnessGas > contract.Gas {
return wantedValueTransferWitnessGas, nil
}
witnessGas = wantedValueTransferWitnessGas
} else if isPrecompile || isSystemContract {
witnessGas = params.WarmStorageReadCostEIP2929 witnessGas = params.WarmStorageReadCostEIP2929
} else {
// 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.
wantedMessageCallWitnessGas := evm.AccessEvents.MessageCallGas(target, contract.Gas-witnessGas)
var overflow bool
if witnessGas, overflow = math.SafeAdd(witnessGas, wantedMessageCallWitnessGas); overflow {
return 0, ErrGasUintOverflow
}
if witnessGas > contract.Gas {
return witnessGas, nil
}
} }
return witnessGas + gas, nil
contract.Gas -= witnessGas
// if the operation fails, adds witness gas to the gas before returning the error
gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
contract.Gas += witnessGas // restore witness gas so that it can be charged at the callsite
var overflow bool
if gas, overflow = math.SafeAdd(gas, witnessGas); overflow {
return 0, ErrGasUintOverflow
}
return gas, err
} }
} }
var ( var (
gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall) gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall, true)
gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode) gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode, false)
gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall) gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall, false)
gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall) gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall, false)
) )
func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
@ -118,15 +116,44 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem
return 0, nil return 0, nil
} }
contractAddr := contract.Address() contractAddr := contract.Address()
statelessGas := evm.AccessEvents.BasicDataGas(contractAddr, false) wanted := evm.AccessEvents.BasicDataGas(contractAddr, false, contract.Gas, false)
if wanted > contract.Gas {
return wanted, nil
}
statelessGas := wanted
balanceIsZero := evm.StateDB.GetBalance(contractAddr).Sign() == 0
_, isPrecompile := evm.precompile(beneficiaryAddr)
isSystemContract := beneficiaryAddr == params.HistoryStorageAddress
if (isPrecompile || isSystemContract) && balanceIsZero {
return statelessGas, nil
}
if contractAddr != beneficiaryAddr { if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, false) wanted := evm.AccessEvents.BasicDataGas(beneficiaryAddr, false, contract.Gas-statelessGas, false)
if wanted > contract.Gas-statelessGas {
return statelessGas + wanted, nil
}
statelessGas += wanted
} }
// Charge write costs if it transfers value // Charge write costs if it transfers value
if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { if !balanceIsZero {
statelessGas += evm.AccessEvents.BasicDataGas(contractAddr, true) wanted := evm.AccessEvents.BasicDataGas(contractAddr, true, contract.Gas-statelessGas, false)
if wanted > contract.Gas-statelessGas {
return statelessGas + wanted, nil
}
statelessGas += wanted
if contractAddr != beneficiaryAddr { if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, true) if evm.StateDB.Exist(beneficiaryAddr) {
wanted = evm.AccessEvents.BasicDataGas(beneficiaryAddr, true, contract.Gas-statelessGas, false)
} else {
wanted = evm.AccessEvents.AddAccount(beneficiaryAddr, true, contract.Gas-statelessGas)
}
if wanted > contract.Gas-statelessGas {
return statelessGas + wanted, nil
}
statelessGas += wanted
} }
} }
return statelessGas, nil return statelessGas, nil
@ -137,17 +164,19 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if err != nil { if err != nil {
return 0, err return 0, err
} }
var (
codeOffset = stack.Back(1)
length = stack.Back(2)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = gomath.MaxUint64
}
_, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64())
if !contract.IsDeployment && !contract.IsSystemCall { if !contract.IsDeployment && !contract.IsSystemCall {
gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) var (
codeOffset = stack.Back(1)
length = stack.Back(2)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = gomath.MaxUint64
}
_, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64())
_, wanted := evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, contract.Gas-gas)
gas += wanted
} }
return gas, nil return gas, nil
} }
@ -158,16 +187,17 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo
if err != nil { if err != nil {
return 0, err return 0, err
} }
if contract.IsSystemCall { addr := common.Address(stack.peek().Bytes20())
_, isPrecompile := evm.precompile(addr)
if isPrecompile || addr == params.HistoryStorageAddress {
var overflow bool
if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil return gas, nil
} }
addr := common.Address(stack.peek().Bytes20()) wgas := evm.AccessEvents.BasicDataGas(addr, false, contract.Gas-gas, true)
wgas := evm.AccessEvents.BasicDataGas(addr, false)
if wgas == 0 {
wgas = params.WarmStorageReadCostEIP2929
}
var overflow bool var overflow bool
// We charge (cold-warm), since 'warm' is already charged as constantGas
if gas, overflow = math.SafeAdd(gas, wgas); overflow { if gas, overflow = math.SafeAdd(gas, wgas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }