core: implement EIP-8037: state creation gas cost increase (new spec)

This commit is contained in:
MariusVanDerWijden 2026-04-27 16:46:00 +02:00
parent a065580422
commit b1daa4432c
27 changed files with 711 additions and 168 deletions

View file

@ -133,7 +133,8 @@ func Transaction(ctx *cli.Context) error {
} }
// Check intrinsic gas // Check intrinsic gas
rules := chainConfig.Rules(common.Big0, true, 0) rules := chainConfig.Rules(common.Big0, true, 0)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) gasCostPerStateByte := core.CostPerStateByte(&types.Header{}, chainConfig)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, gasCostPerStateByte)
if err != nil { if err != nil {
r.Error = err r.Error = err
results = append(results, r) results = append(results, r)
@ -147,7 +148,7 @@ func Transaction(ctx *cli.Context) error {
} }
// For Prague txs, validate the floor data gas. // For Prague txs, validate the floor data gas.
if rules.IsPrague { if rules.IsPrague {
floorDataGas, err := core.FloorDataGas(rules, tx.Data()) floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil { if err != nil {
r.Error = err r.Error = err
results = append(results, r) results = append(results, r)

View file

@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
data := make([]byte, nbytes) data := make([]byte, nbytes)
return func(i int, gen *BlockGen) { return func(i int, gen *BlockGen) {
toaddr := common.Address{} toaddr := common.Address{}
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false) cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, 1)
signer := gen.Signer() signer := gen.Signer()
gasPrice := big.NewInt(0) gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil { if gen.header.BaseFee != nil {

View file

@ -63,12 +63,12 @@ var (
func TestProcessUBT(t *testing.T) { func TestProcessUBT(t *testing.T) {
var ( var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true) intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.TestRules, 1)
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
// will not contain that copied data. // will not contain that copied data.
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true) intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.TestRules, 1)
signer = types.LatestSigner(testUBTChainConfig) signer = types.LatestSigner(testUBTChainConfig)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain

View file

@ -18,6 +18,7 @@ package core
import ( import (
"math/big" "math/big"
"math/bits"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
@ -68,21 +69,50 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
} }
return vm.BlockContext{ return vm.BlockContext{
CanTransfer: CanTransfer, CanTransfer: CanTransfer,
Transfer: Transfer, Transfer: Transfer,
GetHash: GetHashFn(header, chain), GetHash: GetHashFn(header, chain),
Coinbase: beneficiary, Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number), BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time, Time: header.Time,
Difficulty: new(big.Int).Set(header.Difficulty), Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee, BaseFee: baseFee,
BlobBaseFee: blobBaseFee, BlobBaseFee: blobBaseFee,
GasLimit: header.GasLimit, GasLimit: header.GasLimit,
Random: random, Random: random,
SlotNum: slotNum, SlotNum: slotNum,
CostPerStateByte: CostPerStateByte(header, chain.Config()),
} }
} }
// CostPerStateByte computes the cost per one byte of state creation
// after EIP-8037.
func CostPerStateByte(header *types.Header, config *params.ChainConfig) uint64 {
if !config.IsAmsterdam(header.Number, header.Time) {
return 0
}
const (
blocksPerYear uint64 = 2_628_000 // 7200 * 365
offset uint64 = 9578
significantBts uint64 = 5
)
numerator := header.GasLimit * blocksPerYear
denominator := uint64(2) * params.TargetStateGrowthPerYear
raw := (numerator + denominator - 1) / denominator
shifted := raw + offset
// bit length of shifted
bitLen := uint64(64 - bits.LeadingZeros64(shifted))
var shift uint64
if bitLen > significantBts {
shift = bitLen - significantBts
}
quantized := (shifted >> shift) << shift
if quantized > offset {
return quantized - offset
}
return 1
}
// NewEVMTxContext creates a new transaction context for a single transaction. // NewEVMTxContext creates a new transaction context for a single transaction.
func NewEVMTxContext(msg *Message) vm.TxContext { func NewEVMTxContext(msg *Message) vm.TxContext {
ctx := vm.TxContext{ ctx := vm.TxContext{

View file

@ -27,6 +27,11 @@ type GasPool struct {
remaining uint64 remaining uint64
initial uint64 initial uint64
cumulativeUsed uint64 cumulativeUsed uint64
// EIP-8037: per-dimension cumulative sums for Amsterdam.
// Block gas used = max(cumulativeRegular, cumulativeState).
cumulativeRegular uint64
cumulativeState uint64
} }
// NewGasPool initializes the gasPool with the given amount. // NewGasPool initializes the gasPool with the given amount.
@ -68,20 +73,44 @@ func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error {
return nil return nil
} }
// ReturnGasAmsterdam calculates the new remaining gas in the pool after the
// execution of a message.
func (gp *GasPool) ReturnGasAmsterdam(txRegular, txState, receiptGasUsed uint64) error {
gp.cumulativeRegular += txRegular
gp.cumulativeState += txState
gp.cumulativeUsed += receiptGasUsed
blockUsed := max(gp.cumulativeRegular, gp.cumulativeState)
if gp.initial < blockUsed {
return fmt.Errorf("%w: block gas overflow: initial %d, used %d (regular: %d, state: %d)",
ErrGasLimitReached, gp.initial, blockUsed, gp.cumulativeRegular, gp.cumulativeState)
}
// TX inclusion: only the regular dimension is checked when deciding
// whether the next transaction fits.
gp.remaining = gp.initial - gp.cumulativeRegular
return nil
}
// Gas returns the amount of gas remaining in the pool. // Gas returns the amount of gas remaining in the pool.
func (gp *GasPool) Gas() uint64 { func (gp *GasPool) Gas() uint64 {
return gp.remaining return gp.remaining
} }
// CumulativeUsed returns the amount of cumulative consumed gas (refunded included). // CumulativeUsed returns the cumulative gas consumed for receipt tracking.
// For Amsterdam blocks, this is the sum of per-tx tx_gas_used_after_refund
// (what users pay), not the 2D block-level metric.
func (gp *GasPool) CumulativeUsed() uint64 { func (gp *GasPool) CumulativeUsed() uint64 {
return gp.cumulativeUsed return gp.cumulativeUsed
} }
// Used returns the amount of consumed gas. // Used returns the amount of consumed gas. For Amsterdam blocks with
// 2D gas accounting (EIP-8037), returns max(sum_regular, sum_state).
func (gp *GasPool) Used() uint64 { func (gp *GasPool) Used() uint64 {
if gp.cumulativeRegular > 0 || gp.cumulativeState > 0 {
return max(gp.cumulativeRegular, gp.cumulativeState)
}
if gp.initial < gp.remaining { if gp.initial < gp.remaining {
panic("gas used underflow") panic(fmt.Sprintf("gas used underflow: %v %v", gp.initial, gp.remaining))
} }
return gp.initial - gp.remaining return gp.initial - gp.remaining
} }
@ -89,9 +118,11 @@ func (gp *GasPool) Used() uint64 {
// Snapshot returns the deep-copied object as the snapshot. // Snapshot returns the deep-copied object as the snapshot.
func (gp *GasPool) Snapshot() *GasPool { func (gp *GasPool) Snapshot() *GasPool {
return &GasPool{ return &GasPool{
initial: gp.initial, initial: gp.initial,
remaining: gp.remaining, remaining: gp.remaining,
cumulativeUsed: gp.cumulativeUsed, cumulativeUsed: gp.cumulativeUsed,
cumulativeRegular: gp.cumulativeRegular,
cumulativeState: gp.cumulativeState,
} }
} }
@ -100,6 +131,8 @@ func (gp *GasPool) Set(other *GasPool) {
gp.initial = other.initial gp.initial = other.initial
gp.remaining = other.remaining gp.remaining = other.remaining
gp.cumulativeUsed = other.cumulativeUsed gp.cumulativeUsed = other.cumulativeUsed
gp.cumulativeRegular = other.cumulativeRegular
gp.cumulativeState = other.cumulativeState
} }
func (gp *GasPool) String() string { func (gp *GasPool) String() string {

View file

@ -55,12 +55,19 @@ type journal struct {
validRevisions []revision validRevisions []revision
nextRevisionId int nextRevisionId int
// stateBytesCharged caches the state bytes result per snapshot ID.
// When a call boundary computes its state bytes and charges gas,
// the result is stored here. The parent frame subtracts the sum
// of its subcalls' cached results to avoid double-counting.
stateBytesCharged map[int]int64
} }
// newJournal creates a new initialized journal. // newJournal creates a new initialized journal.
func newJournal() *journal { func newJournal() *journal {
return &journal{ return &journal{
dirties: make(map[common.Address]int), dirties: make(map[common.Address]int),
stateBytesCharged: make(map[int]int64),
} }
} }
@ -71,6 +78,7 @@ func (j *journal) reset() {
j.entries = j.entries[:0] j.entries = j.entries[:0]
j.validRevisions = j.validRevisions[:0] j.validRevisions = j.validRevisions[:0]
clear(j.dirties) clear(j.dirties)
clear(j.stateBytesCharged)
j.nextRevisionId = 0 j.nextRevisionId = 0
} }
@ -135,6 +143,101 @@ func (j *journal) length() int {
return len(j.entries) return len(j.entries)
} }
// stateChangedBytes computes the state bytes created by the call frame
// identified by snapshotId. Since subcalls always compute their results
// before the parent (innermost-first), this only scans journal entries
// between this snapshot and the next one — the frame's own entries.
// Subcall results are summed from the cache and subtracted.
//
// The result is cached in stateBytesCharged[snapshotId] so the parent
// frame can look it up instead of re-scanning.
func (j *journal) stateChangedBytes(snapshotId int, stateObjects map[common.Address]*stateObject) int64 {
// TODO (MariusVanDerWijden): this is a bit slop-py needs to be cleaned up
// Resolve snapshot index.
idx := sort.Search(len(j.validRevisions), func(i int) bool {
return j.validRevisions[i].id >= snapshotId
})
if idx == len(j.validRevisions) || j.validRevisions[idx].id != snapshotId {
panic(fmt.Errorf("snapshot id %v not found for stateChangedBytes", snapshotId))
}
start := j.validRevisions[idx].journalIndex
// Our range is [start, end) where end is the next revision's start,
// or the end of the journal if we're the last revision.
end := len(j.entries)
if idx+1 < len(j.validRevisions) {
end = j.validRevisions[idx+1].journalIndex
}
// Walk only our own entries.
type slotKey struct {
addr common.Address
key common.Hash
}
type slotInfo struct {
prev common.Hash // value before first write in this frame
orig common.Hash // committed/original value from trie
}
slots := make(map[slotKey]*slotInfo)
created := make(map[common.Address]bool)
codeChanged := make(map[common.Address]bool)
for i := start; i < end; i++ {
switch e := j.entries[i].(type) {
case createContractChange:
created[e.account] = true
case codeChange:
codeChanged[e.account] = true
case storageChange:
sk := slotKey{e.account, e.key}
if _, seen := slots[sk]; !seen {
slots[sk] = &slotInfo{prev: e.prevvalue, orig: e.origvalue}
}
}
}
var totalBytes int64
for range created {
totalBytes += CostPerAccount
}
for sk, si := range slots {
obj := stateObjects[sk.addr]
if obj == nil {
continue
}
cur := obj.dirtyStorage[sk.key]
prevZero := si.prev == (common.Hash{})
curZero := cur == (common.Hash{})
origZero := si.orig == (common.Hash{})
if prevZero && !curZero && origZero {
// Frame-entry zero, frame-exit non-zero, tx-entry zero:
// this frame created a new slot, charge.
totalBytes += CostPerSlot
} else if !prevZero && curZero && origZero {
// Only refund slots created and freed in this transaction
totalBytes -= CostPerSlot
}
// All other transitions are free:
// - prevZero && !curZero && !origZero: pre-existing slot was
// cleared in earlier frame, re-set here — no charge.
// - X → Y (non-zero to non-zero): no charge.
// - zero → zero: no change.
// - !prevZero && curZero && !origZero: pre-exising slot was
// cleared now, don't refund to not enable gas tokens.
}
for addr := range codeChanged {
obj := stateObjects[addr]
if obj != nil {
totalBytes += int64(len(obj.code))
}
}
// Cache our result so the parent can look it up.
j.stateBytesCharged[snapshotId] = totalBytes
return totalBytes
}
// copy returns a deep-copied journal. // copy returns a deep-copied journal.
func (j *journal) copy() *journal { func (j *journal) copy() *journal {
entries := make([]journalEntry, 0, j.length()) entries := make([]journalEntry, 0, j.length())
@ -142,10 +245,11 @@ func (j *journal) copy() *journal {
entries = append(entries, j.entries[i].copy()) entries = append(entries, j.entries[i].copy())
} }
return &journal{ return &journal{
entries: entries, entries: entries,
dirties: maps.Clone(j.dirties), dirties: maps.Clone(j.dirties),
validRevisions: slices.Clone(j.validRevisions), validRevisions: slices.Clone(j.validRevisions),
nextRevisionId: j.nextRevisionId, nextRevisionId: j.nextRevisionId,
stateBytesCharged: maps.Clone(j.stateBytesCharged),
} }
} }

View file

@ -758,6 +758,18 @@ func (s *StateDB) GetRefund() uint64 {
return s.refund return s.refund
} }
const (
CostPerAccount = 112
CostPerSlot = 32
)
// StateChangedBytes computes the state bytes created since the given snapshot,
// excluding bytes already charged by subcalls. See journal.stateChangedBytes
// for the detailed accounting.
func (s *StateDB) StateChangedBytes(snapshotId int) int64 {
return s.journal.stateChangedBytes(snapshotId, s.stateObjects)
}
type removedAccountWithBalance struct { type removedAccountWithBalance struct {
address common.Address address common.Address
balance *uint256.Int balance *uint256.Int

View file

@ -288,3 +288,7 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
} }
return s.inner.Finalise(deleteEmptyObjects) return s.inner.Finalise(deleteEmptyObjects)
} }
func (s *hookedStateDB) StateChangedBytes(snapshotId int) int64 {
return s.inner.StateChangedBytes(snapshotId)
}

View file

@ -261,7 +261,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
} }
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress) evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudgetReg(30_000_000), common.U2560)
if evm.StateDB.AccessEvents() != nil { if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents) evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
} }
@ -288,7 +288,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
} }
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress) evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudgetReg(30_000_000), common.U2560)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -327,7 +327,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
} }
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudgetReg(30_000_000), common.U2560)
if evm.StateDB.AccessEvents() != nil { if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents) evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
} }

View file

@ -68,13 +68,29 @@ func (result *ExecutionResult) Revert() []byte {
} }
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (vm.GasCosts, error) { // costPerStateByte needs to be set post-Amsterdam.
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
// Set the starting gas for the raw transaction // Set the starting gas for the raw transaction
var gas uint64 var gas vm.GasCosts
if isContractCreation && isHomestead { if isContractCreation && rules.IsHomestead {
gas = params.TxGasContractCreation if rules.IsAmsterdam {
// EIP-8037: account creation is state gas; base tx + CREATE overhead is regular gas.
gas.RegularGas = params.TxGas + params.CreateGasAmsterdam
gas.StateGas = int64(params.AccountCreationSize * costPerStateByte)
} else {
gas.RegularGas = params.TxGasContractCreation
}
} else { } else {
gas = params.TxGas gas.RegularGas = params.TxGas
}
// EIP-8037: authorization tuples contribute both regular and state gas.
if authList != nil {
if rules.IsAmsterdam {
gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas
gas.StateGas += int64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * int64(costPerStateByte)
} else {
gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
}
} }
dataLen := uint64(len(data)) dataLen := uint64(len(data))
// Bump the required gas by the amount of transactional data // Bump the required gas by the amount of transactional data
@ -85,39 +101,60 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
// Make sure we don't exceed uint64 for all data combinations // Make sure we don't exceed uint64 for all data combinations
nonZeroGas := params.TxDataNonZeroGasFrontier nonZeroGas := params.TxDataNonZeroGasFrontier
if isEIP2028 { if rules.IsIstanbul {
nonZeroGas = params.TxDataNonZeroGasEIP2028 nonZeroGas = params.TxDataNonZeroGasEIP2028
} }
if (math.MaxUint64-gas)/nonZeroGas < nz { if (math.MaxUint64-gas.RegularGas)/nonZeroGas < nz {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += nz * nonZeroGas gas.RegularGas += nz * nonZeroGas
if (math.MaxUint64-gas)/params.TxDataZeroGas < z { if (math.MaxUint64-gas.RegularGas)/params.TxDataZeroGas < z {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += z * params.TxDataZeroGas gas.RegularGas += z * params.TxDataZeroGas
if isContractCreation && isEIP3860 { if isContractCreation && rules.IsShanghai {
lenWords := toWordSize(dataLen) lenWords := toWordSize(dataLen)
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords { if (math.MaxUint64-gas.RegularGas)/params.InitCodeWordGas < lenWords {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += lenWords * params.InitCodeWordGas gas.RegularGas += lenWords * params.InitCodeWordGas
} }
} }
if accessList != nil { if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas addresses := uint64(len(accessList))
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas storageKeys := uint64(accessList.StorageKeys())
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListAddressGas < addresses {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas.RegularGas += addresses * params.TxAccessListAddressGas
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListStorageKeyGas < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas.RegularGas += storageKeys * params.TxAccessListStorageKeyGas
// EIP-7981: access list data is charged in addition to the base charge.
if rules.IsAmsterdam {
const (
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
)
if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas.RegularGas += addresses * addressCost
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas.RegularGas += storageKeys * storageKeyCost
}
} }
if authList != nil { return gas, nil
gas += uint64(len(authList)) * params.CallNewAccountGas
}
return vm.GasCosts{RegularGas: gas}, nil
} }
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623). // FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
func FloorDataGas(rules params.Rules, data []byte) (uint64, error) { func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) {
var ( var (
tokens uint64 tokens uint64
tokenCost uint64 tokenCost uint64
@ -127,6 +164,9 @@ func FloorDataGas(rules params.Rules, data []byte) (uint64, error) {
// From 10/40 to 64/64 for zero/non-zero bytes. // From 10/40 to 64/64 for zero/non-zero bytes.
tokens = uint64(len(data)) * params.TxTokenPerNonZeroByte tokens = uint64(len(data)) * params.TxTokenPerNonZeroByte
tokenCost = params.TxCostFloorPerToken7976 tokenCost = params.TxCostFloorPerToken7976
// EIP-7981 adds additional tokens for every entry in the accesslist
tokens += uint64(len(accessList)) * common.AddressLength * params.TxTokenPerNonZeroByte
tokens += uint64(accessList.StorageKeys()) * common.HashLength * params.TxTokenPerNonZeroByte
} else { } else {
var ( var (
z = uint64(bytes.Count(data, []byte{0})) z = uint64(bytes.Count(data, []byte{0}))
@ -282,7 +322,7 @@ func (st *stateTransition) to() common.Address {
return *st.msg.To return *st.msg.To
} }
func (st *stateTransition) buyGas() error { func (st *stateTransition) buyGas() (uint64, error) {
mgval := new(big.Int).SetUint64(st.msg.GasLimit) mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval.Mul(mgval, st.msg.GasPrice) mgval.Mul(mgval, st.msg.GasPrice)
balanceCheck := new(big.Int).Set(mgval) balanceCheck := new(big.Int).Set(mgval)
@ -306,54 +346,57 @@ func (st *stateTransition) buyGas() error {
} }
balanceCheckU256, overflow := uint256.FromBig(balanceCheck) balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
if overflow { if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex()) return 0, fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
} }
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 { if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) return 0, fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err
} }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
} }
st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
st.initialBudget = st.gasRemaining.Copy()
// After Amsterdam we limit the regular gas to 16k, the data gas to the transaction limit
limit := st.msg.GasLimit
if st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time) {
limit = min(st.msg.GasLimit, params.MaxTxGas)
}
st.initialBudget = vm.NewGasBudget(limit, st.msg.GasLimit-limit)
st.gasRemaining = st.initialBudget.Copy()
mgvalU256, _ := uint256.FromBig(mgval) mgvalU256, _ := uint256.FromBig(mgval)
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy) st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
return nil return st.msg.GasLimit, nil
} }
func (st *stateTransition) preCheck() error { func (st *stateTransition) preCheck() (uint64, error) {
// Only check transactions that are not fake // Only check transactions that are not fake
msg := st.msg msg := st.msg
if !msg.SkipNonceChecks { if !msg.SkipNonceChecks {
// Make sure this transaction's nonce is correct. // Make sure this transaction's nonce is correct.
stNonce := st.state.GetNonce(msg.From) stNonce := st.state.GetNonce(msg.From)
if msgNonce := msg.Nonce; stNonce < msgNonce { if msgNonce := msg.Nonce; stNonce < msgNonce {
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh, return 0, fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh,
msg.From.Hex(), msgNonce, stNonce) msg.From.Hex(), msgNonce, stNonce)
} else if stNonce > msgNonce { } else if stNonce > msgNonce {
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, return 0, fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow,
msg.From.Hex(), msgNonce, stNonce) msg.From.Hex(), msgNonce, stNonce)
} else if stNonce+1 < stNonce { } else if stNonce+1 < stNonce {
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, return 0, fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
msg.From.Hex(), stNonce) msg.From.Hex(), stNonce)
} }
} }
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time) isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
if !msg.SkipTransactionChecks { if !msg.SkipTransactionChecks {
// Verify tx gas limit does not exceed EIP-7825 cap. // Verify tx gas limit does not exceed EIP-7825 cap.
if isOsaka && msg.GasLimit > params.MaxTxGas { if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit) return 0, fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
} }
// Make sure the sender is an EOA // Make sure the sender is an EOA
code := st.state.GetCode(msg.From) code := st.state.GetCode(msg.From)
_, delegated := types.ParseDelegation(code) _, delegated := types.ParseDelegation(code)
if len(code) > 0 && !delegated { if len(code) > 0 && !delegated {
return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code)) return 0, fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code))
} }
} }
// Make sure that transaction gasFeeCap is greater than the baseFee (post london) // Make sure that transaction gasFeeCap is greater than the baseFee (post london)
@ -362,21 +405,21 @@ func (st *stateTransition) preCheck() error {
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0 skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
if !skipCheck { if !skipCheck {
if l := msg.GasFeeCap.BitLen(); l > 256 { if l := msg.GasFeeCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh, return 0, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
msg.From.Hex(), l) msg.From.Hex(), l)
} }
if l := msg.GasTipCap.BitLen(); l > 256 { if l := msg.GasTipCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh, return 0, fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
msg.From.Hex(), l) msg.From.Hex(), l)
} }
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 { if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap, return 0, fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap) msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
} }
// This will panic if baseFee is nil, but basefee presence is verified // This will panic if baseFee is nil, but basefee presence is verified
// as part of header validation. // as part of header validation.
if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 { if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow, return 0, fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee) msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
} }
} }
@ -387,17 +430,17 @@ func (st *stateTransition) preCheck() error {
// has it as a non-nillable value, so any msg derived from blob transaction has it non-nil. // has it as a non-nillable value, so any msg derived from blob transaction has it non-nil.
// However, messages created through RPC (eth_call) don't have this restriction. // However, messages created through RPC (eth_call) don't have this restriction.
if msg.To == nil { if msg.To == nil {
return ErrBlobTxCreate return 0, ErrBlobTxCreate
} }
if len(msg.BlobHashes) == 0 { if len(msg.BlobHashes) == 0 {
return ErrMissingBlobHashes return 0, ErrMissingBlobHashes
} }
if isOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs { if isOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs {
return ErrTooManyBlobs return 0, ErrTooManyBlobs
} }
for i, hash := range msg.BlobHashes { for i, hash := range msg.BlobHashes {
if !kzg4844.IsValidVersionedHash(hash[:]) { if !kzg4844.IsValidVersionedHash(hash[:]) {
return fmt.Errorf("blob %d has invalid hash version", i) return 0, fmt.Errorf("blob %d has invalid hash version", i)
} }
} }
} }
@ -410,7 +453,7 @@ func (st *stateTransition) preCheck() error {
// This will panic if blobBaseFee is nil, but blobBaseFee presence // This will panic if blobBaseFee is nil, but blobBaseFee presence
// is verified as part of header validation. // is verified as part of header validation.
if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 { if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 {
return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow, return 0, fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee) msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
} }
} }
@ -419,10 +462,10 @@ func (st *stateTransition) preCheck() error {
// Check that EIP-7702 authorization list signatures are well formed. // Check that EIP-7702 authorization list signatures are well formed.
if msg.SetCodeAuthorizations != nil { if msg.SetCodeAuthorizations != nil {
if msg.To == nil { if msg.To == nil {
return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From) return 0, fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)
} }
if len(msg.SetCodeAuthorizations) == 0 { if len(msg.SetCodeAuthorizations) == 0 {
return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From) return 0, fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
} }
} }
return st.buyGas() return st.buyGas()
@ -450,7 +493,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// 6. caller has enough balance to cover asset transfer for **topmost** call // 6. caller has enough balance to cover asset transfer for **topmost** call
// Check clauses 1-3, buy gas if everything is correct // Check clauses 1-3, buy gas if everything is correct
if err := st.preCheck(); err != nil { gas, err := st.preCheck()
if err != nil {
return nil, err return nil, err
} }
@ -460,26 +504,64 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
contractCreation = msg.To == nil contractCreation = msg.To == nil
floorDataGas uint64 floorDataGas uint64
) )
if !rules.IsAmsterdam {
if err := st.gp.SubGas(gas); err != nil {
return nil, err
}
}
// Check clauses 4-5, subtract intrinsic gas if everything is correct // Check clauses 4-5, subtract intrinsic gas if everything is correct
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Regular gas check for block inclusion post-amsterdam includes state gas.
if rules.IsAmsterdam {
subGasAmount := msg.GasLimit
if subGasAmount > uint64(cost.StateGas) {
subGasAmount -= uint64(cost.StateGas)
} else {
subGasAmount = 0
}
subGasAmount = min(subGasAmount, params.MaxTxGas)
if err := st.gp.SubGas(subGasAmount); err != nil {
return nil, err
}
}
// Compute the floor data cost (EIP-7623), needed for both Prague and Amsterdam validation.
if rules.IsPrague {
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
if err != nil {
return nil, err
}
if msg.GasLimit < floorDataGas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
}
}
if rules.IsAmsterdam {
// EIP-8037: total intrinsic must fit within the transaction gas limit.
if cost.Sum() > msg.GasLimit {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, msg.GasLimit, cost.Sum())
}
// EIP-8037: the regular gas consumption (intrinsic or floor) must fit within MaxTxGas.
maxRegularGas := max(cost.RegularGas, floorDataGas)
if maxRegularGas > params.MaxTxGas {
return nil, fmt.Errorf("%w: max regular gas %d exceeds limit %d", ErrIntrinsicGas, maxRegularGas, params.MaxTxGas)
}
}
prior, sufficient := st.gasRemaining.Charge(cost) prior, sufficient := st.gasRemaining.Charge(cost)
if !sufficient { if !sufficient {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas) return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
} }
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxIntrinsicGas) if rules.IsAmsterdam {
} t.OnGasChange(msg.GasLimit, st.gasRemaining.RegularGas+st.gasRemaining.StateGas, tracing.GasChangeTxIntrinsicGas)
// Gas limit suffices for the floor data cost (EIP-7623) } else {
if rules.IsPrague { t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxIntrinsicGas)
floorDataGas, err = FloorDataGas(rules, msg.Data)
if err != nil {
return nil, err
}
if msg.GasLimit < floorDataGas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
} }
} }
@ -516,6 +598,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
ret []byte ret []byte
vmerr error // vm errors do not effect consensus and are therefore not assigned to err vmerr error // vm errors do not effect consensus and are therefore not assigned to err
) )
// EIP-8037: Take a snapshot for the outer call frame so we can compute
// state gas for state changes made at the transaction level (nonce,
// value transfer, authorizations, and contract creation overhead).
outerSnapshot := st.state.Snapshot()
var execGasUsed vm.GasUsed
if contractCreation { if contractCreation {
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value) ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
} else { } else {
@ -526,7 +614,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if msg.SetCodeAuthorizations != nil { if msg.SetCodeAuthorizations != nil {
for _, auth := range msg.SetCodeAuthorizations { for _, auth := range msg.SetCodeAuthorizations {
// Note errors are ignored, we simply skip invalid authorizations here. // Note errors are ignored, we simply skip invalid authorizations here.
st.applyAuthorization(&auth) st.applyAuthorization(rules, &auth)
} }
} }
@ -543,6 +631,19 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value) ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
} }
// EIP-8037: charge state gas for the outer call frame's own state changes.
if rules.IsAmsterdam {
if vmerr == nil {
outerBytes := st.state.StateChangedBytes(outerSnapshot)
st.gasRemaining.Charge(vm.GasCosts{StateGas: outerBytes * int64(st.evm.Context.CostPerStateByte)})
} else {
if execGasUsed.StateGas > 0 {
st.gasRemaining.StateGas += uint64(execGasUsed.StateGas)
}
execGasUsed.StateGas = 0
}
}
// Record the gas used excluding gas refunds. This value represents the actual // Record the gas used excluding gas refunds. This value represents the actual
// gas allowance required to complete execution. // gas allowance required to complete execution.
peakGasUsed := st.gasUsed() peakGasUsed := st.gasUsed()
@ -553,28 +654,41 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if rules.IsPrague { if rules.IsPrague {
// After EIP-7623: Data-heavy transactions pay the floor gas. // After EIP-7623: Data-heavy transactions pay the floor gas.
if used := st.gasUsed(); used < floorDataGas { if used := st.gasUsed(); used < floorDataGas {
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used}) prev := st.gasRemaining.RegularGas
// When the calldata floor exceeds actual gas used, any
// remaining state gas must also be consumed.
targetRemaining := (st.initialBudget.RegularGas + st.initialBudget.StateGas) - floorDataGas
st.gasRemaining.StateGas = 0
st.gasRemaining.RegularGas = targetRemaining
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor) t.OnGasChange(prev, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor)
} }
} }
if peakGasUsed < floorDataGas { if peakGasUsed < floorDataGas {
peakGasUsed = floorDataGas peakGasUsed = floorDataGas
} }
} }
// Return gas to the user
st.returnGas()
// Return gas to the gas pool returned := st.returnGas()
if rules.IsAmsterdam { if rules.IsAmsterdam {
// Refund is excluded for returning // EIP-8037: 2D gas accounting for Amsterdam.
err = st.gp.ReturnGas(st.initialBudget.RegularGas-peakGasUsed, st.gasUsed()) // tx_regular = intrinsic_regular + exec_regular_gas_used
// tx_state = intrinsic_state (adjusted) + exec_state_gas_used
// execGasUsed.StateGas may be negative when an SSTORE 0→x→0 refund
// exceeded the intrinsic-charged state gas
txState := uint64(cost.StateGas)
if execGasUsed.StateGas > 0 {
txState += uint64(execGasUsed.StateGas)
}
txRegular := cost.RegularGas + execGasUsed.RegularGas
txRegular = max(txRegular, floorDataGas)
if err := st.gp.ReturnGasAmsterdam(txRegular, txState, st.gasUsed()); err != nil {
return nil, err
}
} else { } else {
// Refund is included for returning if err = st.gp.ReturnGas(returned, st.gasUsed()); err != nil {
err = st.gp.ReturnGas(st.gasRemaining.RegularGas, st.gasUsed()) return nil, err
} }
if err != nil {
return nil, err
} }
effectiveTip := msg.GasPrice effectiveTip := msg.GasPrice
if rules.IsLondon { if rules.IsLondon {
@ -587,8 +701,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// are 0. This avoids a negative effectiveTip being applied to // are 0. This avoids a negative effectiveTip being applied to
// the coinbase when simulating calls. // the coinbase when simulating calls.
} else { } else {
fee := new(uint256.Int).SetUint64(st.gasUsed()) // For Amsterdam, the fee is based on what the user pays (receipt gas used).
feeGas := st.gasUsed()
fee := new(uint256.Int).SetUint64(feeGas)
fee.Mul(fee, effectiveTipU256) fee.Mul(fee, effectiveTipU256)
// always read the coinbase account to include it in the BAL (TODO check this is actually part of the spec)
st.state.GetBalance(st.evm.Context.Coinbase)
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee) st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
// 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
@ -601,8 +721,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
st.evm.StateDB.AddLog(log) st.evm.StateDB.AddLog(log)
} }
} }
usedGas := st.gasUsed()
return &ExecutionResult{ return &ExecutionResult{
UsedGas: st.gasUsed(), UsedGas: usedGas,
MaxUsedGas: peakGasUsed, MaxUsedGas: peakGasUsed,
Err: vmerr, Err: vmerr,
ReturnData: ret, ReturnData: ret,
@ -641,30 +762,43 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
} }
// applyAuthorization applies an EIP-7702 code delegation to the state. // applyAuthorization applies an EIP-7702 code delegation to the state.
func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error { func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization) (uint64, error) {
authority, err := st.validateAuthorization(auth) authority, err := st.validateAuthorization(auth)
if err != nil { if err != nil {
return err return 0, err
} }
// If the account already exists in state, refund the new account cost // If the account already exists in state, refund the new account cost
// charged in the intrinsic calculation. // charged in the intrinsic calculation.
var refund uint64
if st.state.Exist(authority) { if st.state.Exist(authority) {
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas) if rules.IsAmsterdam {
// EIP-8037: refund account creation state gas to the reservoir
refund = params.AccountCreationSize * st.evm.Context.CostPerStateByte
st.gasRemaining.StateGas += refund
} else {
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
}
} }
prevDelegation, isDelegated := types.ParseDelegation(st.state.GetCode(authority))
// Update nonce and account code. // Update nonce and account code.
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization) st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
if auth.Address == (common.Address{}) { if auth.Address == (common.Address{}) {
// Delegation to zero address means clear. // Delegation to zero address means clear.
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear) if isDelegated {
return nil st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
}
return refund, nil
} }
// Otherwise install delegation to auth.Address. // install delegation to auth.Address if the delegation changed
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization) if !isDelegated || auth.Address != prevDelegation {
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
}
return nil return refund, nil
} }
// calcRefund computes refund counter, capped to a refund quotient. // calcRefund computes refund counter, capped to a refund quotient.
@ -683,22 +817,25 @@ func (st *stateTransition) calcRefund() vm.GasBudget {
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 { if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, st.gasRemaining.RegularGas+refund, tracing.GasChangeTxRefunds) st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, st.gasRemaining.RegularGas+refund, tracing.GasChangeTxRefunds)
} }
return vm.NewGasBudget(refund) return vm.NewGasBudgetReg(refund)
} }
// returnGas returns ETH for remaining gas, // returnGas returns ETH for remaining gas,
// exchanged at the original rate. // exchanged at the original rate.
func (st *stateTransition) returnGas() { func (st *stateTransition) returnGas() uint64 {
remaining := uint256.NewInt(st.gasRemaining.RegularGas) gas := st.gasRemaining.RegularGas + st.gasRemaining.StateGas
remaining := uint256.NewInt(gas)
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice)) remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining.RegularGas > 0 { if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && gas > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, 0, tracing.GasChangeTxLeftOverReturned) st.evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeTxLeftOverReturned)
} }
return gas
} }
// gasUsed returns the amount of gas used up by the state transition. // gasUsed returns the amount of gas used up by the state transition.
// For Amsterdam (2D gas), this includes both regular and state gas consumed.
func (st *stateTransition) gasUsed() uint64 { func (st *stateTransition) gasUsed() uint64 {
return st.gasRemaining.Used(st.initialBudget) return st.gasRemaining.Used(st.initialBudget)
} }

View file

@ -125,16 +125,26 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
} }
// Ensure the transaction has more gas than the bare minimum needed to cover // Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata // the transaction metadata
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai) gasCostPerStateByte := core.CostPerStateByte(head, opts.Config)
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, gasCostPerStateByte)
if err != nil { if err != nil {
return err return err
} }
if gasCostPerStateByte != 0 {
// We require transactions to pay for 110% of intrinsic gas in order to
// prevent situations where a change in gas limit invalidates a lot
// of transactions in the txpool
if minGas := (intrGas.RegularGas * 10) / 9; tx.Gas() < minGas {
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), minGas)
}
}
if tx.Gas() < intrGas.RegularGas { if tx.Gas() < intrGas.RegularGas {
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas.RegularGas) return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas.RegularGas)
} }
// Ensure the transaction can cover floor data gas. // Ensure the transaction can cover floor data gas.
if rules.IsPrague { if rules.IsPrague {
floorDataGas, err := core.FloorDataGas(rules, tx.Data()) floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil { if err != nil {
return err return err
} }

View file

@ -37,7 +37,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
return return
} }
inWant := string(input) inWant := string(input)
RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas), nil, params.Rules{}) RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas, 0), nil, params.Rules{})
if inHave := string(input); inWant != inHave { if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a) t.Errorf("Precompiled %v modified input data", a)
} }

View file

@ -100,7 +100,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}); err != nil { if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{}); err != nil {
t.Error(err) t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected { } else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@ -122,7 +122,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
gas := test.Gas - 1 gas := test.Gas - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}) _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{})
if err.Error() != "out of gas" { if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err) t.Errorf("Expected error [out of gas], got [%v]", err)
} }
@ -139,7 +139,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) { t.Run(test.Name, func(t *testing.T) {
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}) _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{})
if err.Error() != test.ExpectedError { if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
} }
@ -170,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
start := time.Now() start := time.Now()
for bench.Loop() { for bench.Loop() {
copy(data, in) copy(data, in)
res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas), nil, params.Rules{}) res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas, 0), nil, params.Rules{})
} }
elapsed := uint64(time.Since(start)) elapsed := uint64(time.Since(start))
if elapsed < 1 { if elapsed < 1 {

View file

@ -58,15 +58,16 @@ type BlockContext struct {
GetHash GetHashFunc GetHash GetHashFunc
// Block information // Block information
Coinbase common.Address // Provides information for COINBASE Coinbase common.Address // Provides information for COINBASE
GasLimit uint64 // Provides information for GASLIMIT GasLimit uint64 // Provides information for GASLIMIT
BlockNumber *big.Int // Provides information for NUMBER BlockNumber *big.Int // Provides information for NUMBER
Time uint64 // Provides information for TIME Time uint64 // Provides information for TIME
Difficulty *big.Int // Provides information for DIFFICULTY Difficulty *big.Int // Provides information for DIFFICULTY
BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price) BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price)
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price) BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
Random *common.Hash // Provides information for PREVRANDAO Random *common.Hash // Provides information for PREVRANDAO
SlotNum uint64 // Provides information for SLOTNUM SlotNum uint64 // Provides information for SLOTNUM
CostPerStateByte uint64 // EIP-8037: per-byte state creation cost
} }
// TxContext provides the EVM with information about a transaction. // TxContext provides the EVM with information about a transaction.
@ -316,6 +317,15 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
// TODO: consider clearing up unused snapshots: // TODO: consider clearing up unused snapshots:
//} else { //} else {
// evm.StateDB.DiscardSnapshot(snapshot) // evm.StateDB.DiscardSnapshot(snapshot)
} else if evm.chainRules.IsAmsterdam {
// Charge state costs
bytesCharged := evm.StateDB.StateChangedBytes(snapshot)
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
if !gas.CanAfford(stateGasCost) {
gas.Exhaust()
return ret, gas, ErrOutOfGas
}
gas.Charge(stateGasCost)
} }
return ret, gas, err return ret, gas, err
} }
@ -367,6 +377,14 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
} }
gas.Exhaust() gas.Exhaust()
} }
} else if evm.chainRules.IsAmsterdam {
bytesCharged := evm.StateDB.StateChangedBytes(snapshot)
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
if !gas.CanAfford(stateGasCost) {
gas.Exhaust()
return ret, gas, ErrOutOfGas
}
gas.Charge(stateGasCost)
} }
return ret, gas, err return ret, gas, err
} }
@ -411,6 +429,14 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
} }
gas.Exhaust() gas.Exhaust()
} }
} else if evm.chainRules.IsAmsterdam {
bytesCharged := evm.StateDB.StateChangedBytes(snapshot)
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
if !gas.CanAfford(stateGasCost) {
gas.Exhaust()
return ret, gas, ErrOutOfGas
}
gas.Charge(stateGasCost)
} }
return ret, gas, err return ret, gas, err
} }
@ -555,6 +581,12 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
} }
evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules) evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules)
if evm.chainRules.IsAmsterdam {
// Compute the state changed for the contract init.
evm.StateDB.StateChangedBytes(snapshot)
}
initSnapshot := evm.StateDB.Snapshot()
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, address, value, gas, evm.jumpDests) contract := NewContract(caller, address, value, gas, evm.jumpDests)
@ -570,6 +602,15 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(GasCosts{RegularGas: contract.Gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) contract.UseGas(GasCosts{RegularGas: contract.Gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
} }
} else if evm.chainRules.IsAmsterdam {
// Charge initcode's state changes to the created contract's gas.
bytesCharged := evm.StateDB.StateChangedBytes(initSnapshot)
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
if !contract.Gas.CanAfford(stateGasCost) {
contract.Gas.Exhaust()
return ret, address, contract.Gas, ErrOutOfGas
}
contract.Gas.Charge(stateGasCost)
} }
return ret, address, contract.Gas, err return ret, address, contract.Gas, err
} }

View file

@ -97,7 +97,7 @@ func TestEIP2200(t *testing.T) {
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {}, Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
} }
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
initialGas := NewGasBudget(tt.gaspool) initialGas := NewGasBudget(tt.gaspool, 0)
_, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) _, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int))
if !errors.Is(err, tt.failure) { if !errors.Is(err, tt.failure) {
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
@ -157,7 +157,7 @@ func TestCreateGas(t *testing.T) {
} }
evm := NewEVM(vmctx, statedb, chainConfig, config) evm := NewEVM(vmctx, statedb, chainConfig, config)
initialGas := NewGasBudget(uint64(testGas)) initialGas := NewGasBudget(uint64(testGas), 0)
ret, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) ret, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int))
if err != nil { if err != nil {
return false return false

View file

@ -18,17 +18,29 @@ package vm
import "fmt" import "fmt"
// GasUsed is the per-frame accumulator for gas consumption.
// StateGas is signed, because it can be negative in a 0 -> x -> 0 scenario.
type GasUsed struct {
RegularGas uint64
StateGas int64
}
func (g *GasUsed) Add(costs GasCosts) {
g.RegularGas += costs.RegularGas
g.StateGas += costs.StateGas
}
// GasCosts denotes a vector of gas costs in the // GasCosts denotes a vector of gas costs in the
// multidimensional metering paradigm. It represents the cost // multidimensional metering paradigm. It represents the cost
// charged by an individual operation. // charged by an individual operation.
type GasCosts struct { type GasCosts struct {
RegularGas uint64 RegularGas uint64
StateGas uint64 StateGas int64
} }
// Sum returns the total gas (regular + state). // Sum returns the total gas (regular + state).
func (g GasCosts) Sum() uint64 { func (g GasCosts) Sum() uint64 {
return g.RegularGas + g.StateGas return g.RegularGas + uint64(g.StateGas)
} }
// String returns a visual representation of the gas vector. // String returns a visual representation of the gas vector.
@ -43,23 +55,30 @@ func (g GasCosts) String() string {
type GasBudget struct { type GasBudget struct {
RegularGas uint64 // The leftover gas for execution and state gas usage RegularGas uint64 // The leftover gas for execution and state gas usage
StateGas uint64 // The state gas reservoir StateGas uint64 // The state gas reservoir
// Tracks the gas refunds in this call frame. Needed so we can
// revert the refunds if the call frame reverts.
StateGasRefund uint64
} }
// NewGasBudget creates a GasBudget with the given initial regular gas allowance. // NewGasBudgetReg creates a GasBudget with the given initial regular gas allowance.
func NewGasBudget(gas uint64) GasBudget { func NewGasBudgetReg(gas uint64) GasBudget {
return GasBudget{RegularGas: gas} return GasBudget{RegularGas: gas}
} }
// Used returns the amount of regular gas consumed so far. // NewGasBudget creates a GasBudget with the given regular and state gas allowances.
func (g GasBudget) Used(initial GasBudget) uint64 { func NewGasBudget(regular, state uint64) GasBudget {
return initial.RegularGas - g.RegularGas return GasBudget{RegularGas: regular, StateGas: state}
} }
// Exhaust sets all remaining gas to zero, preserving the initial amount // Used returns the total amount of gas consumed so far (regular + state).
// for usage tracking. func (g GasBudget) Used(initial GasBudget) uint64 {
return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas)
}
// Exhaust burns the remaining regular gas on exceptional halt.
func (g *GasBudget) Exhaust() { func (g *GasBudget) Exhaust() {
g.RegularGas = 0 g.RegularGas = 0
g.StateGas = 0
} }
func (g *GasBudget) Copy() GasBudget { func (g *GasBudget) Copy() GasBudget {
@ -72,26 +91,51 @@ func (g GasBudget) String() string {
} }
// CanAfford reports whether the budget has sufficient gas to cover the cost. // CanAfford reports whether the budget has sufficient gas to cover the cost.
// When state gas exceeds the reservoir, the excess spills to regular gas.
func (g GasBudget) CanAfford(cost GasCosts) bool { func (g GasBudget) CanAfford(cost GasCosts) bool {
return g.RegularGas >= cost.RegularGas if g.RegularGas < cost.RegularGas {
return false
}
if cost.StateGas < 0 {
return true
}
if uint64(cost.StateGas) > g.StateGas {
spillover := uint64(cost.StateGas) - g.StateGas
if spillover > g.RegularGas-cost.RegularGas {
return false
}
}
return true
} }
// Charge deducts the given gas cost from the budget. It returns the // Charge deducts the given gas cost from the budget. It returns the
// pre-charge gas value and false if the budget does not have sufficient // pre-charge regular gas value and false if the budget does not have
// gas to cover the cost. // sufficient gas to cover the cost.
func (g *GasBudget) Charge(cost GasCosts) (uint64, bool) { func (g *GasBudget) Charge(cost GasCosts) (uint64, bool) {
prior := g.RegularGas prior := g.RegularGas
if prior < cost.RegularGas { if !g.CanAfford(cost) {
return prior, false return prior, false
} }
g.RegularGas -= cost.RegularGas g.RegularGas -= cost.RegularGas
if cost.StateGas < 0 {
g.StateGas -= uint64(cost.StateGas)
return prior, true
}
if uint64(cost.StateGas) > g.StateGas {
spillover := uint64(cost.StateGas) - g.StateGas
g.StateGas = 0
g.RegularGas -= spillover
} else {
g.StateGas -= uint64(cost.StateGas)
}
return prior, true return prior, true
} }
// Refund adds the given gas budget back. It returns the pre-refund gas // Refund adds the given gas budget back. It returns the pre-refund regular gas
// value and whether the budget was actually changed. // value and whether the budget was actually changed.
func (g *GasBudget) Refund(other GasBudget) (uint64, bool) { func (g *GasBudget) Refund(other GasBudget) (uint64, bool) {
prior := g.RegularGas prior := g.RegularGas
g.RegularGas += other.RegularGas g.RegularGas += other.RegularGas
return prior, g.RegularGas != prior g.StateGas += other.StateGas
return prior, other.RegularGas != 0 || other.StateGas != 0
} }

View file

@ -669,7 +669,7 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation) scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, NewGasBudget(gas), &value) res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, NewGasBudgetReg(gas), &value)
// Push item on the stack based on the returned error. If the ruleset is // Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only // homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must // rule) and treat as an error, if the ruleset is frontier we must
@ -710,7 +710,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2) scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, NewGasBudget(gas), res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, NewGasBudgetReg(gas),
&endowment, &salt) &endowment, &salt)
// Push item on the stack based on the returned error. // Push item on the stack based on the returned error.
if suberr != nil { if suberr != nil {
@ -747,7 +747,7 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if !value.IsZero() { if !value.IsZero() {
gas += params.CallStipend gas += params.CallStipend
} }
ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value) ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, NewGasBudgetReg(gas), &value)
if err != nil { if err != nil {
temp.Clear() temp.Clear()
@ -781,7 +781,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
gas += params.CallStipend gas += params.CallStipend
} }
ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value) ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, NewGasBudgetReg(gas), &value)
if err != nil { if err != nil {
temp.Clear() temp.Clear()
} else { } else {
@ -810,7 +810,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory. // Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, NewGasBudget(gas), scope.Contract.value) ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, NewGasBudgetReg(gas), scope.Contract.value)
if err != nil { if err != nil {
temp.Clear() temp.Clear()
} else { } else {
@ -839,7 +839,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory. // Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, NewGasBudget(gas)) ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, NewGasBudgetReg(gas))
if err != nil { if err != nil {
temp.Clear() temp.Clear()
} else { } else {

View file

@ -99,4 +99,8 @@ type StateDB interface {
// Finalise must be invoked at the end of a transaction // Finalise must be invoked at the end of a transaction
Finalise(bool) *bal.StateAccessList Finalise(bool) *bal.StateAccessList
// StateChangedBytes returns the number of state bytes created since the
// given snapshot. Used by EIP-8037 for state gas metering.
StateChangedBytes(snapshotId int) int64
} }

View file

@ -55,7 +55,7 @@ func TestLoopInterrupt(t *testing.T) {
timeout := make(chan bool) timeout := make(chan bool)
go func(evm *EVM) { go func(evm *EVM) {
_, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64), new(uint256.Int)) _, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64, 0), new(uint256.Int))
errChannel <- err errChannel <- err
}(evm) }(evm)
@ -85,7 +85,7 @@ func BenchmarkInterpreter(b *testing.B) {
value = uint256.NewInt(0) value = uint256.NewInt(0)
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas), nil) contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas, 0), nil)
) )
stack.push(uint256.NewInt(123)) stack.push(uint256.NewInt(123))
stack.push(uint256.NewInt(123)) stack.push(uint256.NewInt(123))

View file

@ -148,7 +148,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
cfg.Origin, cfg.Origin,
common.BytesToAddress([]byte("contract")), common.BytesToAddress([]byte("contract")),
input, input,
vm.NewGasBudget(cfg.GasLimit), vm.NewGasBudgetReg(cfg.GasLimit),
uint256.MustFromBig(cfg.Value), uint256.MustFromBig(cfg.Value),
) )
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
@ -182,7 +182,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
code, address, leftOverGas, err := vmenv.Create( code, address, leftOverGas, err := vmenv.Create(
cfg.Origin, cfg.Origin,
input, input,
vm.NewGasBudget(cfg.GasLimit), vm.NewGasBudgetReg(cfg.GasLimit),
uint256.MustFromBig(cfg.Value), uint256.MustFromBig(cfg.Value),
) )
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
@ -217,7 +217,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
cfg.Origin, cfg.Origin,
address, address,
input, input,
vm.NewGasBudget(cfg.GasLimit), vm.NewGasBudgetReg(cfg.GasLimit),
uint256.MustFromBig(cfg.Value), uint256.MustFromBig(cfg.Value),
) )
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {

View file

@ -2111,3 +2111,116 @@ func runGetBlobs(t testing.TB, getBlobs getBlobsFn, start, limit int, fillRandom
t.Fatalf("Unexpected result for case %s", name) t.Fatalf("Unexpected result for case %s", name)
} }
} }
// TestFailedContractCreationGas tests that a failed contract creation under
// Amsterdam/EIP-8037 computes the correct gasUsed in the receipt. This
// reproduces a cross-client mismatch where Nethermind and geth disagree on
// the receipt root due to different gasUsed for a failing CREATE tx.
func TestFailedContractCreationGas(t *testing.T) {
config := *params.MergedTestChainConfig
// The exact init code from the devnet tx that fails with RETURNDATACOPY OOB
initCode := common.Hex2Bytes("7fd13216074149a6e9713267dccbbc37024275d51076bec33169b0a400a8975d657f000000000000000000000000000000000000000000000000000000000000001e5b6202ffff168161ffff1691502061bfc41446717a6cd1bc37217702e2aac1e77e6e8a75f67b6202ffff16816202ffff1691508261ffff1692503e435b61093d62ae347360835960d24a865b7700b64f1bcf1ada04bb8d96db5221a5fa497115d4b936fdd76202ffff168161ffff169150205b8364a804f9fd32877f00000000000000000000000000000000000000000000000000000000000000016000527f00000000000000000000000000000000000000000000000000000000000000026020527f00000000000000000000000000000000000000000000000000000000000001456040527f1050130679e8fbd2a4b3ebdcb21747c5f6efbaecad13f9b45361510394c098c06060526040608060806000600060065af160805160a0515b6202ffff168161ffff169150fd965b606d9a6202ffff1653831c7c5f8bb81ca4686beebb952b5f20db9445c190332b394f1669b230b104fd75f86956a39ab4b498be875bb61ff1412424a8b675c0a1316202ffff165c496b68a43600766e14ae19e7cbcc93060304845b19446202ffff165d02426202ffff168161ffff169150a100")
gspec := &core.Genesis{
Config: &config,
Alloc: types.GenesisAlloc{
testAddr: {Balance: new(big.Int).Mul(big.NewInt(1e6), big.NewInt(params.Ether))},
params.BeaconRootsAddress: {Balance: common.Big0, Code: params.BeaconRootsCode},
params.HistoryStorageAddress: {Balance: common.Big0, Code: params.HistoryStorageCode},
params.WithdrawalQueueAddress: {Balance: common.Big0, Code: params.WithdrawalQueueCode},
params.ConsolidationQueueAddress: {Balance: common.Big0, Code: params.ConsolidationQueueCode},
config.DepositContractAddress: {Balance: common.Big0},
},
Difficulty: common.Big0,
BaseFee: big.NewInt(params.InitialBaseFee),
GasLimit: 60_000_000,
}
n, ethservice := startEthService(t, gspec, nil)
defer n.Close()
api := newConsensusAPIWithoutHeartbeat(ethservice)
parent := ethservice.BlockChain().CurrentBlock()
signer := types.LatestSigner(ethservice.BlockChain().Config())
// Create the failing contract creation tx (gas=1000000, value=51417)
tx, _ := types.SignTx(types.NewContractCreation(
0,
big.NewInt(51417),
1000000,
big.NewInt(2*params.InitialBaseFee),
initCode,
), signer, testKey)
ethservice.TxPool().Add([]*types.Transaction{tx}, false)
slotNumber := uint64(1)
beaconRoot := common.Hash{0x01}
args := &miner.BuildPayloadArgs{
Parent: parent.Hash(),
Timestamp: parent.Time + 12,
FeeRecipient: common.Address{0xfe},
Random: common.Hash{0xaa},
Withdrawals: []*types.Withdrawal{},
BeaconRoot: &beaconRoot,
SlotNum: &slotNumber,
}
payload, err := api.eth.Miner().BuildPayload(context.Background(), args, false)
if err != nil {
t.Fatalf("BuildPayload failed: %v", err)
}
envelope := payload.ResolveFull()
execPayload := envelope.ExecutionPayload
// Validate via newPayload
resp, err := api.newPayload(context.Background(), *execPayload, []common.Hash{}, &beaconRoot, envelope.Requests, false)
if err != nil {
t.Fatalf("newPayload error: %v", err)
}
if resp.Status != engine.VALID {
t.Fatalf("newPayload returned %s: %v", resp.Status, resp.ValidationError)
}
// Set head
fcState := engine.ForkchoiceStateV1{
HeadBlockHash: execPayload.BlockHash,
SafeBlockHash: execPayload.BlockHash,
FinalizedBlockHash: execPayload.BlockHash,
}
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
t.Fatalf("FCU error: %v", err)
}
// Get the receipt and check gasUsed
block := ethservice.BlockChain().GetBlockByHash(execPayload.BlockHash)
if block == nil {
t.Fatal("block not found after import")
}
receipts := ethservice.BlockChain().GetReceiptsByHash(block.Hash())
if len(receipts) == 0 {
t.Fatal("no receipts found")
}
// Find the contract creation receipt
for i, r := range receipts {
if r.ContractAddress != (common.Address{}) || r.Status == types.ReceiptStatusFailed {
t.Logf("Receipt %d: status=%d gasUsed=%d cumulative=%d contract=%s",
i, r.Status, r.GasUsed, r.CumulativeGasUsed, r.ContractAddress.Hex())
// The tx should fail (RETURNDATACOPY OOB)
if r.Status != types.ReceiptStatusFailed {
t.Errorf("expected failed receipt, got status %d", r.Status)
}
// Check gasUsed makes sense under EIP-8037
// For a failed tx: all regular gas is burned, but state gas
// should NOT be charged (since no state was created).
// gasUsed should be < tx.gas if there's any state gas component.
t.Logf("Tx gas limit: %d", tx.Gas())
if r.GasUsed > tx.Gas() {
t.Errorf("gasUsed %d exceeds tx gas limit %d", r.GasUsed, tx.Gas())
}
t.Logf("Gas difference (limit - used): %d", tx.Gas()-r.GasUsed)
}
}
}

View file

@ -55,7 +55,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
gasLimit uint64 = 31000 gasLimit uint64 = 31000
startGas uint64 = 10000 startGas uint64 = 10000
value = uint256.NewInt(0) value = uint256.NewInt(0)
contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas), nil) contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas, 0), nil)
) )
evm.SetTxContext(vmctx.txCtx) evm.SetTxContext(vmctx.txCtx)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}

View file

@ -47,7 +47,7 @@ func TestStoreCapture(t *testing.T) {
var ( var (
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()}) evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()})
contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000), nil) contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000, 0), nil)
) )
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
var index common.Hash var index common.Hash

View file

@ -1341,6 +1341,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
} }
// Prevent redundant operations if args contain more authorizations than EVM may handle // Prevent redundant operations if args contain more authorizations than EVM may handle
// TODO change with EIP-8037
maxAuthorizations := uint64(*args.Gas) / params.CallNewAccountGas maxAuthorizations := uint64(*args.Gas) / params.CallNewAccountGas
if uint64(len(args.AuthorizationList)) > maxAuthorizations { if uint64(len(args.AuthorizationList)) > maxAuthorizations {
return nil, 0, nil, errors.New("insufficient gas to process all authorizations") return nil, 0, nil, errors.New("insufficient gas to process all authorizations")

View file

@ -88,6 +88,7 @@ const (
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
Create2Gas uint64 = 32000 // Once per CREATE2 operation Create2Gas uint64 = 32000 // Once per CREATE2 operation
CreateGasAmsterdam uint64 = 9000 // Regular gas portion of CREATE in Amsterdam (EIP-8037); state gas is charged separately.
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation.
MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
@ -100,6 +101,7 @@ const (
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702 TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037
// These have been changed during the course of the chain // These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
@ -186,6 +188,13 @@ const (
HistoryServeWindow = 8191 // Number of blocks to serve historical block hashes for, EIP-2935. HistoryServeWindow = 8191 // Number of blocks to serve historical block hashes for, EIP-2935.
MaxBlockSize = 8_388_608 // maximum size of an RLP-encoded block MaxBlockSize = 8_388_608 // maximum size of an RLP-encoded block
TargetStateGrowthPerYear = 100 * 1024 * 1024 * 1024 // 100GB
AccountCreationSize = 112
StorageCreationSize = 32
AuthorizationCreationSize = 23
GasBlockAccessListItem = 2000 // EIP-7928: gas cost per BAL item for gas limit check
) )
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation // Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation

View file

@ -326,7 +326,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
b.StartTimer() b.StartTimer()
start := time.Now() start := time.Now()
initialGas := vm.NewGasBudget(msg.GasLimit) initialGas := vm.NewGasBudget(msg.GasLimit, 0)
// Execute the message. // Execute the message.
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), uint256.MustFromBig(msg.Value)) _, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), uint256.MustFromBig(msg.Value))

View file

@ -81,7 +81,7 @@ func (tt *TransactionTest) Run() error {
return return
} }
// Intrinsic gas // Intrinsic gas
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, 1)
if err != nil { if err != nil {
return return
} }
@ -92,7 +92,7 @@ func (tt *TransactionTest) Run() error {
if rules.IsPrague { if rules.IsPrague {
var floorDataGas uint64 var floorDataGas uint64
floorDataGas, err = core.FloorDataGas(rules, tx.Data()) floorDataGas, err = core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil { if err != nil {
return return
} }