mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-08 07:58:40 +00:00
Merge branch 'eip-8037-rewrite' into bal-devnet-5
This commit is contained in:
commit
869aad082a
37 changed files with 1216 additions and 189 deletions
|
|
@ -133,7 +133,8 @@ func Transaction(ctx *cli.Context) error {
|
|||
}
|
||||
// Check intrinsic gas
|
||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules)
|
||||
gasCostPerStateByte := core.CostPerStateByte(&types.Header{}, chainConfig)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, gasCostPerStateByte)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
|||
data := make([]byte, nbytes)
|
||||
return func(i int, gen *BlockGen) {
|
||||
toaddr := common.Address{}
|
||||
cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{})
|
||||
cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, 1)
|
||||
signer := gen.Signer()
|
||||
gasPrice := big.NewInt(0)
|
||||
if gen.header.BaseFee != nil {
|
||||
|
|
|
|||
|
|
@ -63,12 +63,12 @@ var (
|
|||
func TestProcessUBT(t *testing.T) {
|
||||
var (
|
||||
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.TestRules)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.TestRules, 1)
|
||||
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
|
||||
// will not contain that copied data.
|
||||
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
||||
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.TestRules)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.TestRules, 1)
|
||||
signer = types.LatestSigner(testUBTChainConfig)
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
||||
|
|
|
|||
25
core/evm.go
25
core/evm.go
|
|
@ -68,18 +68,19 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
|||
}
|
||||
|
||||
return vm.BlockContext{
|
||||
CanTransfer: CanTransfer,
|
||||
Transfer: Transfer,
|
||||
GetHash: GetHashFn(header, chain),
|
||||
Coinbase: beneficiary,
|
||||
BlockNumber: new(big.Int).Set(header.Number),
|
||||
Time: header.Time,
|
||||
Difficulty: new(big.Int).Set(header.Difficulty),
|
||||
BaseFee: baseFee,
|
||||
BlobBaseFee: blobBaseFee,
|
||||
GasLimit: header.GasLimit,
|
||||
Random: random,
|
||||
SlotNum: slotNum,
|
||||
CanTransfer: CanTransfer,
|
||||
Transfer: Transfer,
|
||||
GetHash: GetHashFn(header, chain),
|
||||
Coinbase: beneficiary,
|
||||
BlockNumber: new(big.Int).Set(header.Number),
|
||||
Time: header.Time,
|
||||
Difficulty: new(big.Int).Set(header.Difficulty),
|
||||
BaseFee: baseFee,
|
||||
BlobBaseFee: blobBaseFee,
|
||||
GasLimit: header.GasLimit,
|
||||
Random: random,
|
||||
SlotNum: slotNum,
|
||||
CostPerStateByte: CostPerStateByte(header, chain.Config()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ type GasPool struct {
|
|||
remaining uint64
|
||||
initial 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.
|
||||
|
|
@ -68,20 +73,44 @@ func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error {
|
|||
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.
|
||||
func (gp *GasPool) Gas() uint64 {
|
||||
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 {
|
||||
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 {
|
||||
if gp.cumulativeRegular > 0 || gp.cumulativeState > 0 {
|
||||
return max(gp.cumulativeRegular, gp.cumulativeState)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
@ -89,9 +118,11 @@ func (gp *GasPool) Used() uint64 {
|
|||
// Snapshot returns the deep-copied object as the snapshot.
|
||||
func (gp *GasPool) Snapshot() *GasPool {
|
||||
return &GasPool{
|
||||
initial: gp.initial,
|
||||
remaining: gp.remaining,
|
||||
cumulativeUsed: gp.cumulativeUsed,
|
||||
initial: gp.initial,
|
||||
remaining: gp.remaining,
|
||||
cumulativeUsed: gp.cumulativeUsed,
|
||||
cumulativeRegular: gp.cumulativeRegular,
|
||||
cumulativeState: gp.cumulativeState,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +131,8 @@ func (gp *GasPool) Set(other *GasPool) {
|
|||
gp.initial = other.initial
|
||||
gp.remaining = other.remaining
|
||||
gp.cumulativeUsed = other.cumulativeUsed
|
||||
gp.cumulativeRegular = other.cumulativeRegular
|
||||
gp.cumulativeState = other.cumulativeState
|
||||
}
|
||||
|
||||
func (gp *GasPool) String() string {
|
||||
|
|
|
|||
|
|
@ -27,9 +27,24 @@ import (
|
|||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// frameRange is a half-open interval [start, end) of journal entry indices,
|
||||
// used to record the slice of entries occupied by a closed child call frame.
|
||||
type frameRange struct {
|
||||
start, end int
|
||||
}
|
||||
|
||||
type revision struct {
|
||||
id int
|
||||
journalIndex int
|
||||
// closedChildren holds the [start, end) ranges of child call frames that
|
||||
// have been closed under this revision via closeSnapshot. Together with
|
||||
// journalIndex (this frame's own start) and the current journal length
|
||||
// (this frame's tentative end) they describe the slice of entries that
|
||||
// belong directly to this frame, with descendant frames' entries excluded.
|
||||
//
|
||||
// Invariant: ranges are appended in increasing order, are non-overlapping,
|
||||
// and lie entirely within [journalIndex, len(entries)).
|
||||
closedChildren []frameRange
|
||||
}
|
||||
|
||||
// journalEntry is a modification entry in the state change journal that can be
|
||||
|
|
@ -55,12 +70,19 @@ type journal struct {
|
|||
|
||||
validRevisions []revision
|
||||
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.
|
||||
func newJournal() *journal {
|
||||
return &journal{
|
||||
dirties: make(map[common.Address]int),
|
||||
dirties: make(map[common.Address]int),
|
||||
stateBytesCharged: make(map[int]int64),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +93,7 @@ func (j *journal) reset() {
|
|||
j.entries = j.entries[:0]
|
||||
j.validRevisions = j.validRevisions[:0]
|
||||
clear(j.dirties)
|
||||
clear(j.stateBytesCharged)
|
||||
j.nextRevisionId = 0
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +101,7 @@ func (j *journal) reset() {
|
|||
func (j *journal) snapshot() int {
|
||||
id := j.nextRevisionId
|
||||
j.nextRevisionId++
|
||||
j.validRevisions = append(j.validRevisions, revision{id, j.length()})
|
||||
j.validRevisions = append(j.validRevisions, revision{id: id, journalIndex: j.length()})
|
||||
return id
|
||||
}
|
||||
|
||||
|
|
@ -98,6 +121,64 @@ func (j *journal) revertToSnapshot(revid int, s *StateDB) {
|
|||
j.validRevisions = j.validRevisions[:idx]
|
||||
}
|
||||
|
||||
// closeSnapshot marks the end of the call frame identified by revid without
|
||||
// reverting any state. The frame's entry range [snapshot_index, current_length)
|
||||
// is recorded on its parent revision so callers can later iterate the parent's
|
||||
// own entries while skipping over closed children (and, transitively, their
|
||||
// descendants — descendant ranges are absorbed into the closing child's range
|
||||
// when the descendant itself was closed earlier under that child).
|
||||
//
|
||||
// closeSnapshot must be invoked in LIFO order: revid must identify the topmost
|
||||
// snapshot. It panics otherwise. The corresponding revision is popped, so a
|
||||
// subsequent revertToSnapshot on the same id is no longer valid.
|
||||
func (j *journal) closeSnapshot(revid int) {
|
||||
if len(j.validRevisions) == 0 {
|
||||
panic(fmt.Errorf("revision id %v cannot be closed: no open snapshot", revid))
|
||||
}
|
||||
top := len(j.validRevisions) - 1
|
||||
if j.validRevisions[top].id != revid {
|
||||
panic(fmt.Errorf("revision id %v cannot be closed: top is %v",
|
||||
revid, j.validRevisions[top].id))
|
||||
}
|
||||
closed := frameRange{
|
||||
start: j.validRevisions[top].journalIndex,
|
||||
end: len(j.entries),
|
||||
}
|
||||
// Only propagate non-empty ranges, and only if there is a parent frame to
|
||||
// receive them. The outermost frame has nothing to bubble up to.
|
||||
if closed.start < closed.end && top > 0 {
|
||||
parent := &j.validRevisions[top-1]
|
||||
parent.closedChildren = append(parent.closedChildren, closed)
|
||||
}
|
||||
// Drop this revision's bookkeeping. The slice is reused by the parent so
|
||||
// avoid pinning it via the popped tail.
|
||||
j.validRevisions[top].closedChildren = nil
|
||||
j.validRevisions = j.validRevisions[:top]
|
||||
}
|
||||
|
||||
// frameEntries invokes visit for each entry that belongs directly to the
|
||||
// current (topmost) call frame, skipping entries that lie within any closed
|
||||
// child frame's range. Entries are visited in append order. If no frame is
|
||||
// open, frameEntries is a no-op.
|
||||
//
|
||||
// nolint:unused
|
||||
func (j *journal) frameEntries(visit func(entry journalEntry)) {
|
||||
if len(j.validRevisions) == 0 {
|
||||
return
|
||||
}
|
||||
rev := j.validRevisions[len(j.validRevisions)-1]
|
||||
idx := rev.journalIndex
|
||||
for _, child := range rev.closedChildren {
|
||||
for ; idx < child.start; idx++ {
|
||||
visit(j.entries[idx])
|
||||
}
|
||||
idx = child.end
|
||||
}
|
||||
for ; idx < len(j.entries); idx++ {
|
||||
visit(j.entries[idx])
|
||||
}
|
||||
}
|
||||
|
||||
// append inserts a new modification entry to the end of the change journal.
|
||||
func (j *journal) append(entry journalEntry) {
|
||||
j.entries = append(j.entries, entry)
|
||||
|
|
@ -135,17 +216,132 @@ func (j *journal) length() int {
|
|||
return len(j.entries)
|
||||
}
|
||||
|
||||
// stateChangedBytes computes the state bytes created by the call frame
|
||||
// identified by revid, walking only entries that belong directly to this
|
||||
// frame and skipping over closed child frame ranges. The result is cached
|
||||
// in stateBytesCharged so that parent frames can look it up.
|
||||
//
|
||||
// When excludeSubcalls is true, cached subcall costs are not added to the
|
||||
// total. This is useful when subcalls have already been charged to their
|
||||
// own gas budgets and shouldn't bubble up to the ancestor frames.
|
||||
func (j *journal) stateChangedBytes(revid int, stateObjects map[common.Address]*stateObject, excludeSubcalls bool) int64 {
|
||||
// Find the revision by ID.
|
||||
idx := sort.Search(len(j.validRevisions), func(i int) bool {
|
||||
return j.validRevisions[i].id >= revid
|
||||
})
|
||||
if idx == len(j.validRevisions) || j.validRevisions[idx].id != revid {
|
||||
panic(fmt.Errorf("revision id %v not found for stateChangedBytes", revid))
|
||||
}
|
||||
rev := j.validRevisions[idx]
|
||||
|
||||
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)
|
||||
|
||||
// Walk only this frame's own entries, skipping closed child ranges.
|
||||
// Add cached subcall costs from closedChildren.
|
||||
var subcallBytes int64
|
||||
visit := func(e journalEntry) {
|
||||
switch e := e.(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}
|
||||
}
|
||||
}
|
||||
}
|
||||
pos := rev.journalIndex
|
||||
for _, child := range rev.closedChildren {
|
||||
for ; pos < child.start; pos++ {
|
||||
visit(j.entries[pos])
|
||||
}
|
||||
if !excludeSubcalls {
|
||||
// Add the cached cost for this subcall.
|
||||
subcallBytes += j.stateBytesCharged[child.start]
|
||||
}
|
||||
pos = child.end
|
||||
}
|
||||
for ; pos < len(j.entries); pos++ {
|
||||
visit(j.entries[pos])
|
||||
}
|
||||
|
||||
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-existing 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))
|
||||
}
|
||||
}
|
||||
|
||||
// Add subcall costs to get the total for this frame (own + children).
|
||||
totalBytes += subcallBytes
|
||||
|
||||
// Cache so the parent can look up this frame's total cost.
|
||||
j.stateBytesCharged[rev.journalIndex] = totalBytes
|
||||
return totalBytes
|
||||
}
|
||||
|
||||
// copy returns a deep-copied journal.
|
||||
func (j *journal) copy() *journal {
|
||||
entries := make([]journalEntry, 0, j.length())
|
||||
for i := 0; i < j.length(); i++ {
|
||||
entries = append(entries, j.entries[i].copy())
|
||||
}
|
||||
revisions := make([]revision, len(j.validRevisions))
|
||||
for i, r := range j.validRevisions {
|
||||
revisions[i] = revision{
|
||||
id: r.id,
|
||||
journalIndex: r.journalIndex,
|
||||
closedChildren: slices.Clone(r.closedChildren),
|
||||
}
|
||||
}
|
||||
return &journal{
|
||||
entries: entries,
|
||||
dirties: maps.Clone(j.dirties),
|
||||
validRevisions: slices.Clone(j.validRevisions),
|
||||
nextRevisionId: j.nextRevisionId,
|
||||
entries: entries,
|
||||
dirties: maps.Clone(j.dirties),
|
||||
validRevisions: revisions,
|
||||
nextRevisionId: j.nextRevisionId,
|
||||
stateBytesCharged: maps.Clone(j.stateBytesCharged),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
213
core/state/journal_test.go
Normal file
213
core/state/journal_test.go
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// tagEntry is a minimal journalEntry used by journal tests. It carries an
|
||||
// integer tag so frameEntries iteration order can be verified, and is a no-op
|
||||
// on revert so the surrounding StateDB can be a zero value.
|
||||
type tagEntry struct {
|
||||
tag int
|
||||
}
|
||||
|
||||
func (t tagEntry) revert(*StateDB) {}
|
||||
func (t tagEntry) dirtied() (common.Address, bool) { return common.Address{}, false }
|
||||
func (t tagEntry) copy() journalEntry { return t }
|
||||
|
||||
// frameTags drives frameEntries and returns the visited tags in order.
|
||||
func frameTags(j *journal) []int {
|
||||
var got []int
|
||||
j.frameEntries(func(e journalEntry) {
|
||||
got = append(got, e.(tagEntry).tag)
|
||||
})
|
||||
return got
|
||||
}
|
||||
|
||||
// didPanic reports whether fn panicked.
|
||||
func didPanic(fn func()) (panicked bool) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
panicked = true
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
return false
|
||||
}
|
||||
|
||||
// TestJournalFrameTracking covers the happy paths of closeSnapshot and
|
||||
// frameEntries together: basic single-child filtering, empty-range elision,
|
||||
// multiple siblings, transitive descendant absorption, and the no-open-frame
|
||||
// edge case for frameEntries. Building one composite scenario and asserting
|
||||
// at each step keeps the expected behaviour as a connected story rather than
|
||||
// scattering it across many tiny tests.
|
||||
func TestJournalFrameTracking(t *testing.T) {
|
||||
j := newJournal()
|
||||
|
||||
// frameEntries on an empty journal is a no-op.
|
||||
if got := frameTags(j); len(got) != 0 {
|
||||
t.Fatalf("empty journal frameEntries: have %v, want []", got)
|
||||
}
|
||||
|
||||
j.snapshot()
|
||||
j.append(tagEntry{1}) // outer
|
||||
|
||||
// Closing an empty child frame must not record a degenerate range.
|
||||
empty := j.snapshot()
|
||||
j.closeSnapshot(empty)
|
||||
if got := j.validRevisions[0].closedChildren; len(got) != 0 {
|
||||
t.Fatalf("empty child should not propagate, have %+v", got)
|
||||
}
|
||||
|
||||
// First sibling child: two entries, then close. Range goes onto outer.
|
||||
c1 := j.snapshot()
|
||||
c1Start := len(j.entries)
|
||||
j.append(tagEntry{10})
|
||||
j.append(tagEntry{11})
|
||||
c1End := len(j.entries)
|
||||
j.closeSnapshot(c1)
|
||||
|
||||
j.append(tagEntry{2}) // outer between siblings
|
||||
|
||||
// Second sibling, with a grandchild closed inside it. After the
|
||||
// grandchild closes, more entries appear in the child before it itself
|
||||
// closes. The outer must end up with a single range that covers the
|
||||
// child (which transitively covers the grandchild).
|
||||
c2 := j.snapshot()
|
||||
c2Start := len(j.entries)
|
||||
j.append(tagEntry{20})
|
||||
|
||||
gc := j.snapshot()
|
||||
j.append(tagEntry{300})
|
||||
j.closeSnapshot(gc)
|
||||
|
||||
j.append(tagEntry{21})
|
||||
c2End := len(j.entries)
|
||||
j.closeSnapshot(c2)
|
||||
|
||||
j.append(tagEntry{3}) // outer after both siblings
|
||||
|
||||
got := j.validRevisions[0].closedChildren
|
||||
want := []frameRange{{c1Start, c1End}, {c2Start, c2End}}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("closedChildren: have %+v, want %+v", got, want)
|
||||
}
|
||||
if tags := frameTags(j); !slices.Equal(tags, []int{1, 2, 3}) {
|
||||
t.Fatalf("frameEntries: have %v, want [1 2 3]", tags)
|
||||
}
|
||||
|
||||
// Closing the outermost (no-parent) frame is allowed: there is nothing
|
||||
// to populate, but the revision is still popped and its range silently
|
||||
// dropped. The journal ends up with no open frames.
|
||||
outer := j.validRevisions[0].id
|
||||
j.closeSnapshot(outer)
|
||||
if len(j.validRevisions) != 0 {
|
||||
t.Fatalf("after closing outermost, have %d open revisions, want 0", len(j.validRevisions))
|
||||
}
|
||||
}
|
||||
|
||||
// TestJournalCloseSnapshotPanics asserts the LIFO precondition: closing when
|
||||
// no snapshot is open, or closing a revision while a more recent snapshot is
|
||||
// still open above it, must panic rather than silently mutate state. Closing
|
||||
// the outermost (no-parent) frame *is* permitted and is covered in
|
||||
// TestJournalFrameTracking.
|
||||
func TestJournalCloseSnapshotPanics(t *testing.T) {
|
||||
j := newJournal()
|
||||
if !didPanic(func() { j.closeSnapshot(0) }) {
|
||||
t.Fatal("closing with no open snapshot should panic")
|
||||
}
|
||||
bottom := j.snapshot()
|
||||
j.snapshot() // a more recent snapshot is now on top
|
||||
if !didPanic(func() { j.closeSnapshot(bottom) }) {
|
||||
t.Fatal("closing a snapshot that is not the most recent should panic")
|
||||
}
|
||||
}
|
||||
|
||||
// TestJournalRevertInteractions verifies the two cross-cuts between revert
|
||||
// and close: reverting a parent that has absorbed closed children also
|
||||
// throws away the children's entries, and reverting a child (rather than
|
||||
// closing it) leaves no closed-child range on the parent.
|
||||
func TestJournalRevertInteractions(t *testing.T) {
|
||||
t.Run("revertParentWithClosedChild", func(t *testing.T) {
|
||||
j := newJournal()
|
||||
outer := j.snapshot()
|
||||
j.append(tagEntry{1})
|
||||
|
||||
c := j.snapshot()
|
||||
j.append(tagEntry{10})
|
||||
j.append(tagEntry{11})
|
||||
j.closeSnapshot(c)
|
||||
|
||||
j.append(tagEntry{2})
|
||||
j.revertToSnapshot(outer, &StateDB{})
|
||||
|
||||
if len(j.entries) != 0 || len(j.validRevisions) != 0 {
|
||||
t.Fatalf("after revert have entries=%d revisions=%d, want both 0",
|
||||
len(j.entries), len(j.validRevisions))
|
||||
}
|
||||
})
|
||||
t.Run("revertedChildLeavesNoRange", func(t *testing.T) {
|
||||
j := newJournal()
|
||||
j.snapshot()
|
||||
j.append(tagEntry{1})
|
||||
|
||||
c := j.snapshot()
|
||||
j.append(tagEntry{10})
|
||||
j.revertToSnapshot(c, &StateDB{})
|
||||
j.append(tagEntry{2})
|
||||
|
||||
if got := j.validRevisions[0].closedChildren; len(got) != 0 {
|
||||
t.Fatalf("reverted child should not appear in closedChildren, have %+v", got)
|
||||
}
|
||||
if tags := frameTags(j); !slices.Equal(tags, []int{1, 2}) {
|
||||
t.Fatalf("frameEntries: have %v, want [1 2]", tags)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestJournalCopyAndReset checks that the bookkeeping for closed-child ranges
|
||||
// participates in journal.copy (deep-copied, not aliased) and journal.reset
|
||||
// (cleared along with everything else).
|
||||
func TestJournalCopyAndReset(t *testing.T) {
|
||||
j := newJournal()
|
||||
j.snapshot()
|
||||
j.append(tagEntry{1})
|
||||
c := j.snapshot()
|
||||
j.append(tagEntry{10})
|
||||
j.closeSnapshot(c)
|
||||
|
||||
cp := j.copy()
|
||||
if !slices.Equal(cp.validRevisions[0].closedChildren, j.validRevisions[0].closedChildren) {
|
||||
t.Fatalf("copy lost closedChildren: orig=%+v copy=%+v",
|
||||
j.validRevisions[0].closedChildren, cp.validRevisions[0].closedChildren)
|
||||
}
|
||||
cp.validRevisions[0].closedChildren = append(cp.validRevisions[0].closedChildren, frameRange{99, 100})
|
||||
if len(j.validRevisions[0].closedChildren) != 1 {
|
||||
t.Fatal("original aliased copy's closedChildren slice")
|
||||
}
|
||||
|
||||
j.reset()
|
||||
if len(j.entries) != 0 || len(j.validRevisions) != 0 {
|
||||
t.Fatalf("after reset have entries=%d revisions=%d, want both 0",
|
||||
len(j.entries), len(j.validRevisions))
|
||||
}
|
||||
}
|
||||
|
|
@ -342,7 +342,7 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
|
|||
if i%8 == 0 {
|
||||
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
|
||||
}
|
||||
if i > 50 || i < 85 {
|
||||
if i > 50 && i < 85 {
|
||||
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
|
||||
}
|
||||
if i%64 == 0 {
|
||||
|
|
|
|||
|
|
@ -762,11 +762,87 @@ func (s *StateDB) RevertToSnapshot(revid int) {
|
|||
s.journal.revertToSnapshot(revid, s)
|
||||
}
|
||||
|
||||
// CloseSnapshot marks the call frame identified by revid as completed without
|
||||
// reverting any state. Its journal entry range is recorded on the parent
|
||||
// frame so the parent can later iterate its own entries while skipping over
|
||||
// closed children. revid must identify the topmost open snapshot (i.e. frames
|
||||
// must be closed in LIFO order). It panics otherwise.
|
||||
func (s *StateDB) CloseSnapshot(revid int) {
|
||||
s.journal.closeSnapshot(revid)
|
||||
}
|
||||
|
||||
// GetRefund returns the current value of the refund counter.
|
||||
func (s *StateDB) GetRefund() uint64 {
|
||||
return s.refund
|
||||
}
|
||||
|
||||
const (
|
||||
CostPerAccount = 112
|
||||
CostPerSlot = 32
|
||||
)
|
||||
|
||||
// StateChangedBytes computes the state bytes created by the call frame
|
||||
// identified by revid, excluding entries from closed child frames. When
|
||||
// excludeSubcalls is true, cached subcall costs are not added to the total.
|
||||
func (s *StateDB) StateChangedBytes(revid int, excludeSubcalls bool) int64 {
|
||||
return s.journal.stateChangedBytes(revid, s.stateObjects, excludeSubcalls)
|
||||
}
|
||||
|
||||
// SelfDestructRefundBytes computes the total state bytes to refund at tx-end
|
||||
// for accounts that were both created and selfdestructed during this
|
||||
// transaction.
|
||||
func (s *StateDB) SelfDestructRefundBytes() int64 {
|
||||
// Collect addresses created and selfdestructed in this tx.
|
||||
targets := make(map[common.Address]*stateObject)
|
||||
for addr, obj := range s.stateObjects {
|
||||
if s.IsNewContract(addr) && s.HasSelfDestructed(addr) {
|
||||
targets[addr] = obj
|
||||
}
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
return 0
|
||||
}
|
||||
// Account creation + code deposit refunds.
|
||||
var bytes int64
|
||||
for _, obj := range targets {
|
||||
bytes += CostPerAccount + int64(len(obj.code))
|
||||
}
|
||||
// For storage slots: walk journal storage entries to find the tx-entry
|
||||
// value of each slot. Count slots where tx-entry was zero and the final
|
||||
// dirty value is non-zero (i.e. the slot was charged as new and not
|
||||
// subsequently cleared).
|
||||
type slotKey struct {
|
||||
addr common.Address
|
||||
key common.Hash
|
||||
}
|
||||
originAtTxEntry := make(map[slotKey]common.Hash)
|
||||
for _, e := range s.journal.entries {
|
||||
sc, ok := e.(storageChange)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := targets[sc.account]; !ok {
|
||||
continue
|
||||
}
|
||||
sk := slotKey{sc.account, sc.key}
|
||||
if _, seen := originAtTxEntry[sk]; !seen {
|
||||
originAtTxEntry[sk] = sc.origvalue
|
||||
}
|
||||
}
|
||||
for sk, orig := range originAtTxEntry {
|
||||
if orig != (common.Hash{}) {
|
||||
continue
|
||||
}
|
||||
obj := targets[sk.addr]
|
||||
cur, dirty := obj.dirtyStorage[sk.key]
|
||||
if !dirty || cur == (common.Hash{}) {
|
||||
continue
|
||||
}
|
||||
bytes += CostPerSlot
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
type removedAccountWithBalance struct {
|
||||
address common.Address
|
||||
balance *uint256.Int
|
||||
|
|
|
|||
|
|
@ -148,6 +148,10 @@ func (s *hookedStateDB) RevertToSnapshot(i int) {
|
|||
s.inner.RevertToSnapshot(i)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) CloseSnapshot(i int) {
|
||||
s.inner.CloseSnapshot(i)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Snapshot() int {
|
||||
return s.inner.Snapshot()
|
||||
}
|
||||
|
|
@ -289,3 +293,11 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) (*bal.StateAccessList,
|
|||
}
|
||||
return s.inner.Finalise(deleteEmptyObjects)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) StateChangedBytes(revid int, excludeSubcalls bool) int64 {
|
||||
return s.inner.StateChangedBytes(revid, excludeSubcalls)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) SelfDestructRefundBytes() int64 {
|
||||
return s.inner.SelfDestructRefundBytes()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,6 +274,19 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *
|
|||
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm)
|
||||
}
|
||||
|
||||
// systemCallGasBudget returns the gas budget for system calls. Pre-Amsterdam
|
||||
// the budget is 30M regular gas. Post-Amsterdam (EIP-8037), an additional
|
||||
// state-gas reservoir of `STATE_BYTES_PER_STORAGE_SET × CPSB × SYSTEM_MAX_SSTORES_PER_CALL`
|
||||
// is provided to cover the expected new SSTOREs in system contracts.
|
||||
func systemCallGasBudget(evm *vm.EVM) vm.GasBudget {
|
||||
const regular = 30_000_000
|
||||
if evm.ChainConfig().IsAmsterdam(evm.Context.BlockNumber, evm.Context.Time) {
|
||||
stateGas := params.StorageCreationSize * evm.Context.CostPerStateByte * params.SystemMaxSstoresPerCall
|
||||
return vm.NewGasBudget(regular, stateGas)
|
||||
}
|
||||
return vm.NewGasBudgetReg(regular)
|
||||
}
|
||||
|
||||
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
|
||||
// contract. This method is exported to be used in tests.
|
||||
func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) (*bal.StateAccessList, *bal.StateMutations) {
|
||||
|
|
@ -294,7 +307,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) (*bal.StateAcce
|
|||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
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, systemCallGasBudget(evm), common.U2560)
|
||||
if evm.StateDB.AccessEvents() != nil {
|
||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
|
|
@ -321,7 +334,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) (*bal.StateAccess
|
|||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
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, systemCallGasBudget(evm), common.U2560)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -360,7 +373,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
|||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
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, systemCallGasBudget(evm), common.U2560)
|
||||
if evm.StateDB.AccessEvents() != nil {
|
||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,13 +68,29 @@ func (result *ExecutionResult) Revert() []byte {
|
|||
}
|
||||
|
||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, rules params.Rules) (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
|
||||
var gas uint64
|
||||
var gas vm.GasCosts
|
||||
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 {
|
||||
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))
|
||||
// Bump the required gas by the amount of transactional data
|
||||
|
|
@ -88,35 +104,35 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
if rules.IsIstanbul {
|
||||
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
||||
}
|
||||
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
||||
if (math.MaxUint64-gas.RegularGas)/nonZeroGas < nz {
|
||||
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
|
||||
}
|
||||
gas += z * params.TxDataZeroGas
|
||||
gas.RegularGas += z * params.TxDataZeroGas
|
||||
|
||||
if isContractCreation && rules.IsShanghai {
|
||||
lenWords := toWordSize(dataLen)
|
||||
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
|
||||
if (math.MaxUint64-gas.RegularGas)/params.InitCodeWordGas < lenWords {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += lenWords * params.InitCodeWordGas
|
||||
gas.RegularGas += lenWords * params.InitCodeWordGas
|
||||
}
|
||||
}
|
||||
if accessList != nil {
|
||||
addresses := uint64(len(accessList))
|
||||
storageKeys := uint64(accessList.StorageKeys())
|
||||
if (math.MaxUint64-gas)/params.TxAccessListAddressGas < addresses {
|
||||
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListAddressGas < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += addresses * params.TxAccessListAddressGas
|
||||
if (math.MaxUint64-gas)/params.TxAccessListStorageKeyGas < storageKeys {
|
||||
gas.RegularGas += addresses * params.TxAccessListAddressGas
|
||||
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListStorageKeyGas < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += storageKeys * params.TxAccessListStorageKeyGas
|
||||
gas.RegularGas += storageKeys * params.TxAccessListStorageKeyGas
|
||||
|
||||
// EIP-7981: access list data is charged in addition to the base charge.
|
||||
if rules.IsAmsterdam {
|
||||
|
|
@ -124,17 +140,17 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
)
|
||||
if (math.MaxUint64-gas)/addressCost < addresses {
|
||||
if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += addresses * addressCost
|
||||
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
|
||||
gas.RegularGas += addresses * addressCost
|
||||
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += storageKeys * storageKeyCost
|
||||
gas.RegularGas += storageKeys * storageKeyCost
|
||||
}
|
||||
}
|
||||
return vm.GasCosts{RegularGas: gas}, nil
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
|
|
@ -306,7 +322,7 @@ func (st *stateTransition) to() common.Address {
|
|||
return *st.msg.To
|
||||
}
|
||||
|
||||
func (st *stateTransition) buyGas() error {
|
||||
func (st *stateTransition) buyGas() (uint64, error) {
|
||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
||||
mgval.Mul(mgval, st.msg.GasPrice)
|
||||
balanceCheck := new(big.Int).Set(mgval)
|
||||
|
|
@ -330,54 +346,57 @@ func (st *stateTransition) buyGas() error {
|
|||
}
|
||||
balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
|
||||
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 {
|
||||
return 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
|
||||
return 0, fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
||||
}
|
||||
|
||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
|
||||
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)
|
||||
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
|
||||
msg := st.msg
|
||||
if !msg.SkipNonceChecks {
|
||||
// Make sure this transaction's nonce is correct.
|
||||
stNonce := st.state.GetNonce(msg.From)
|
||||
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)
|
||||
} 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)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// Verify tx gas limit does not exceed EIP-7825 cap.
|
||||
if isOsaka && msg.GasLimit > params.MaxTxGas {
|
||||
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||
if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
|
||||
return 0, fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||
}
|
||||
// Make sure the sender is an EOA
|
||||
code := st.state.GetCode(msg.From)
|
||||
_, delegated := types.ParseDelegation(code)
|
||||
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)
|
||||
|
|
@ -386,21 +405,21 @@ func (st *stateTransition) preCheck() error {
|
|||
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
||||
if !skipCheck {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
// This will panic if baseFee is nil, but basefee presence is verified
|
||||
// as part of header validation.
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -411,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.
|
||||
// However, messages created through RPC (eth_call) don't have this restriction.
|
||||
if msg.To == nil {
|
||||
return ErrBlobTxCreate
|
||||
return 0, ErrBlobTxCreate
|
||||
}
|
||||
if len(msg.BlobHashes) == 0 {
|
||||
return ErrMissingBlobHashes
|
||||
return 0, ErrMissingBlobHashes
|
||||
}
|
||||
if isOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs {
|
||||
return ErrTooManyBlobs
|
||||
return 0, ErrTooManyBlobs
|
||||
}
|
||||
for i, hash := range msg.BlobHashes {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -434,7 +453,7 @@ func (st *stateTransition) preCheck() error {
|
|||
// This will panic if blobBaseFee is nil, but blobBaseFee presence
|
||||
// is verified as part of header validation.
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -443,10 +462,10 @@ func (st *stateTransition) preCheck() error {
|
|||
// Check that EIP-7702 authorization list signatures are well formed.
|
||||
if msg.SetCodeAuthorizations != 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 {
|
||||
return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
|
||||
return 0, fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
|
||||
}
|
||||
}
|
||||
return st.buyGas()
|
||||
|
|
@ -474,7 +493,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// 6. caller has enough balance to cover asset transfer for **topmost** call
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
@ -484,19 +504,34 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
contractCreation = msg.To == nil
|
||||
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
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules)
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prior, sufficient := st.gasRemaining.Charge(cost)
|
||||
if !sufficient {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
|
||||
t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
// Gas limit suffices for the floor data cost (EIP-7623)
|
||||
|
||||
// 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 {
|
||||
|
|
@ -507,6 +542,29 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if !sufficient {
|
||||
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 rules.IsAmsterdam {
|
||||
t.OnGasChange(msg.GasLimit, st.gasRemaining.RegularGas+st.gasRemaining.StateGas, tracing.GasChangeTxIntrinsicGas)
|
||||
} else {
|
||||
t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
}
|
||||
|
||||
if rules.IsEIP4762 {
|
||||
st.evm.AccessEvents.AddTxOrigin(msg.From)
|
||||
|
||||
|
|
@ -540,6 +598,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
ret []byte
|
||||
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||
)
|
||||
|
||||
// Take a snapshot for gas calculation
|
||||
outerSnapshot := st.state.Snapshot()
|
||||
|
||||
var execGasUsed vm.GasUsed
|
||||
if contractCreation {
|
||||
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
|
||||
} else {
|
||||
|
|
@ -550,7 +613,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
if msg.SetCodeAuthorizations != nil {
|
||||
for _, auth := range msg.SetCodeAuthorizations {
|
||||
// Note errors are ignored, we simply skip invalid authorizations here.
|
||||
st.applyAuthorization(&auth)
|
||||
st.applyAuthorization(rules, &auth)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -567,6 +630,21 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
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, false)
|
||||
// Refund state gas for selfdestructed accounts.
|
||||
outerBytes -= st.state.SelfDestructRefundBytes()
|
||||
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
|
||||
// gas allowance required to complete execution.
|
||||
peakGasUsed := st.gasUsed()
|
||||
|
|
@ -577,28 +655,41 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
if rules.IsPrague {
|
||||
// After EIP-7623: Data-heavy transactions pay the floor gas.
|
||||
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 {
|
||||
t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor)
|
||||
t.OnGasChange(prev, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor)
|
||||
}
|
||||
}
|
||||
if peakGasUsed < floorDataGas {
|
||||
peakGasUsed = floorDataGas
|
||||
}
|
||||
}
|
||||
// Return gas to the user
|
||||
st.returnGas()
|
||||
|
||||
// Return gas to the gas pool
|
||||
returned := st.returnGas()
|
||||
if rules.IsAmsterdam {
|
||||
// Refund is excluded for returning
|
||||
err = st.gp.ReturnGas(st.initialBudget.RegularGas-peakGasUsed, st.gasUsed())
|
||||
// EIP-8037: 2D gas accounting for Amsterdam.
|
||||
// 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 {
|
||||
// Refund is included for returning
|
||||
err = st.gp.ReturnGas(st.gasRemaining.RegularGas, st.gasUsed())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err = st.gp.ReturnGas(returned, st.gasUsed()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
effectiveTip := msg.GasPrice
|
||||
if rules.IsLondon {
|
||||
|
|
@ -611,8 +702,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// are 0. This avoids a negative effectiveTip being applied to
|
||||
// the coinbase when simulating calls.
|
||||
} 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// add the coinbase to the witness iff the fee is greater than 0
|
||||
|
|
@ -625,8 +722,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
st.evm.StateDB.AddLog(log)
|
||||
}
|
||||
}
|
||||
usedGas := st.gasUsed()
|
||||
return &ExecutionResult{
|
||||
UsedGas: st.gasUsed(),
|
||||
UsedGas: usedGas,
|
||||
MaxUsedGas: peakGasUsed,
|
||||
Err: vmerr,
|
||||
ReturnData: ret,
|
||||
|
|
@ -665,30 +763,43 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
|
|||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// If the account already exists in state, refund the new account cost
|
||||
// charged in the intrinsic calculation.
|
||||
if st.state.Exist(authority) {
|
||||
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
||||
// If the account is not empty (per EIP-161: non-zero nonce, balance, or
|
||||
// code) refund the new account cost charged in the intrinsic calculation.
|
||||
var refund uint64
|
||||
if !st.state.Empty(authority) {
|
||||
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.
|
||||
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
|
||||
if auth.Address == (common.Address{}) {
|
||||
// Delegation to zero address means clear.
|
||||
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
||||
return nil
|
||||
if isDelegated {
|
||||
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
||||
}
|
||||
return refund, nil
|
||||
}
|
||||
|
||||
// Otherwise install delegation to auth.Address.
|
||||
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
||||
// install delegation to auth.Address if the delegation changed
|
||||
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.
|
||||
|
|
@ -707,22 +818,25 @@ func (st *stateTransition) calcRefund() vm.GasBudget {
|
|||
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)
|
||||
}
|
||||
return vm.NewGasBudget(refund)
|
||||
return vm.NewGasBudgetReg(refund)
|
||||
}
|
||||
|
||||
// returnGas returns ETH for remaining gas,
|
||||
// exchanged at the original rate.
|
||||
func (st *stateTransition) returnGas() {
|
||||
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
|
||||
func (st *stateTransition) returnGas() uint64 {
|
||||
gas := st.gasRemaining.RegularGas + st.gasRemaining.StateGas
|
||||
remaining := uint256.NewInt(gas)
|
||||
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
||||
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 {
|
||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, 0, tracing.GasChangeTxLeftOverReturned)
|
||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && gas > 0 {
|
||||
st.evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeTxLeftOverReturned)
|
||||
}
|
||||
return gas
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return st.gasRemaining.Used(st.initialBudget)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,13 +125,23 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the transaction has more gas than the bare minimum needed to cover
|
||||
// the transaction metadata
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules)
|
||||
gasCostPerStateByte := core.CostPerStateByte(head, opts.Config)
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, gasCostPerStateByte)
|
||||
if err != nil {
|
||||
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 {
|
||||
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas.RegularGas)
|
||||
}
|
||||
|
||||
// Ensure the transaction can cover floor data gas.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||
|
|
|
|||
|
|
@ -162,12 +162,11 @@ func (e BlockAccessList) ValidateGasLimit(blockGasLimit uint64) error {
|
|||
// Hash computes the keccak256 hash of the access list
|
||||
func (e *BlockAccessList) Hash() common.Hash {
|
||||
var enc bytes.Buffer
|
||||
err := e.EncodeRLP(&enc)
|
||||
if err != nil {
|
||||
// errors here are related to BAL values exceeding maximum size defined
|
||||
// by the spec. Hard-fail because these cases are not expected to be hit
|
||||
// under reasonable conditions.
|
||||
panic(err)
|
||||
if err := e.EncodeRLP(&enc); err != nil {
|
||||
// Errors here are related to BAL values exceeding maximum size defined
|
||||
// by the spec. Return empty hash because these cases are not expected
|
||||
// to be hit under reasonable conditions.
|
||||
return common.Hash{}
|
||||
}
|
||||
/*
|
||||
bal, err := json.MarshalIndent(e.StringableRepresentation(), "", " ")
|
||||
|
|
@ -431,7 +430,7 @@ func (e *AccountAccess) Copy() AccountAccess {
|
|||
res := AccountAccess{
|
||||
Address: e.Address,
|
||||
StorageReads: slices.Clone(e.StorageReads),
|
||||
BalanceChanges: slices.Clone(e.BalanceChanges),
|
||||
BalanceChanges: make([]encodingBalanceChange, 0, len(e.BalanceChanges)),
|
||||
NonceChanges: slices.Clone(e.NonceChanges),
|
||||
}
|
||||
for _, storageWrite := range e.StorageChanges {
|
||||
|
|
@ -477,6 +476,7 @@ func (a *ConstructionAccountAccesses) toEncodingObj(addr common.Address) Account
|
|||
indices := slices.Collect(maps.Keys(slotWrites))
|
||||
slices.SortFunc(indices, cmp.Compare[uint32])
|
||||
for _, index := range indices {
|
||||
val := slotWrites[index]
|
||||
obj.Accesses = append(obj.Accesses, encodingStorageWrite{
|
||||
TxIdx: index,
|
||||
ValueAfter: NewEncodedStorageFromHash(slotWrites[index]),
|
||||
|
|
@ -536,7 +536,7 @@ func (c *ConstructionBlockAccessList) ToEncodingObj() *BlockAccessList {
|
|||
}
|
||||
slices.SortFunc(addresses, common.Address.Cmp)
|
||||
|
||||
var res BlockAccessList
|
||||
res := make(BlockAccessList, 0, len(addresses))
|
||||
for _, addr := range addresses {
|
||||
res = append(res, c.list[addr].toEncodingObj(addr))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
package bal
|
||||
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
import "github.com/ethereum/go-ethereum/rlp"
|
||||
import "github.com/holiman/uint256"
|
||||
import "io"
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
func (obj *AccountAccess) EncodeRLP(_w io.Writer) error {
|
||||
w := rlp.NewEncoderBuffer(_w)
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ func TestBALEncoding(t *testing.T) {
|
|||
t.Fatalf("encoding failed: %v\n", err)
|
||||
}
|
||||
var dec BlockAccessList
|
||||
if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 10000000)); err != nil {
|
||||
if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil {
|
||||
t.Fatalf("decoding failed: %v\n", err)
|
||||
}
|
||||
if dec.Hash() != bal.ToEncodingObj().Hash() {
|
||||
|
|
@ -114,7 +114,11 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
|||
storageReads []common.Hash
|
||||
balances []encodingBalanceChange
|
||||
nonces []encodingAccountNonce
|
||||
codes []encodingCodeChange
|
||||
)
|
||||
randSlot := func() *uint256.Int {
|
||||
return new(uint256.Int).SetBytes(testrand.Bytes(32))
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
slot := encodingSlotWrites{
|
||||
Slot: NewEncodedStorageFromHash(testrand.Hash()),
|
||||
|
|
@ -139,7 +143,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
|||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
storageReads = append(storageReads, testrand.Hash())
|
||||
storageReads = append(storageReads, randSlot())
|
||||
}
|
||||
if sort {
|
||||
slices.SortFunc(storageReads, func(a, b common.Hash) int {
|
||||
|
|
@ -200,7 +204,7 @@ func makeTestBAL(sort bool) BlockAccessList {
|
|||
return bytes.Compare(a.Address[:], b.Address[:])
|
||||
})
|
||||
}
|
||||
return list
|
||||
return &list
|
||||
}
|
||||
|
||||
func TestBlockAccessListCopy(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
|
|||
return
|
||||
}
|
||||
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 {
|
||||
t.Errorf("Precompiled %v modified input data", a)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
|||
in := common.Hex2Bytes(test.Input)
|
||||
gas := p.RequiredGas(in)
|
||||
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)
|
||||
} else if common.Bytes2Hex(res) != test.Expected {
|
||||
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
|
||||
|
||||
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" {
|
||||
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)
|
||||
gas := p.RequiredGas(in)
|
||||
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 {
|
||||
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()
|
||||
for bench.Loop() {
|
||||
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))
|
||||
if elapsed < 1 {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ var activators = map[int]func(*JumpTable){
|
|||
7939: enable7939,
|
||||
8024: enable8024,
|
||||
7843: enable7843,
|
||||
8037: enable8037,
|
||||
}
|
||||
|
||||
// EnableEIP enables the given EIP on the config.
|
||||
|
|
@ -169,6 +170,13 @@ func enable3529(jt *JumpTable) {
|
|||
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529
|
||||
}
|
||||
|
||||
// enable8037 enables EIP-8037 SSTORE repricing: the regular-gas portion of
|
||||
// new slot creation and same-tx 0→X→0 reset is reduced; the state-gas
|
||||
// portion is charged/refunded at frame-end via the journal.
|
||||
func enable8037(jt *JumpTable) {
|
||||
jt[SSTORE].dynamicGas = gasSStoreEIP8037
|
||||
}
|
||||
|
||||
// enable3198 applies EIP-3198 (BASEFEE Opcode)
|
||||
// - Adds an opcode that returns the current block's base fee.
|
||||
func enable3198(jt *JumpTable) {
|
||||
|
|
|
|||
111
core/vm/evm.go
111
core/vm/evm.go
|
|
@ -58,15 +58,16 @@ type BlockContext struct {
|
|||
GetHash GetHashFunc
|
||||
|
||||
// Block information
|
||||
Coinbase common.Address // Provides information for COINBASE
|
||||
GasLimit uint64 // Provides information for GASLIMIT
|
||||
BlockNumber *big.Int // Provides information for NUMBER
|
||||
Time uint64 // Provides information for TIME
|
||||
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)
|
||||
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
|
||||
SlotNum uint64 // Provides information for SLOTNUM
|
||||
Coinbase common.Address // Provides information for COINBASE
|
||||
GasLimit uint64 // Provides information for GASLIMIT
|
||||
BlockNumber *big.Int // Provides information for NUMBER
|
||||
Time uint64 // Provides information for TIME
|
||||
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)
|
||||
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
|
||||
SlotNum uint64 // Provides information for SLOTNUM
|
||||
CostPerStateByte uint64 // EIP-8037: per-byte state creation cost
|
||||
}
|
||||
|
||||
// TxContext provides the EVM with information about a transaction.
|
||||
|
|
@ -254,7 +255,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
}
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
snapshot1 := evm.StateDB.Snapshot()
|
||||
p, isPrecompile := evm.precompile(addr)
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
|
||||
|
|
@ -267,7 +268,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
// Thus, only pay for the creation of the code hash leaf here.
|
||||
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false)
|
||||
if _, ok := gas.Charge(GasCosts{RegularGas: wgas}); !ok {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
gas.Exhaust()
|
||||
return nil, gas, ErrOutOfGas
|
||||
}
|
||||
|
|
@ -275,6 +276,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
||||
// Calling a non-existing account, don't do anything.
|
||||
evm.StateDB.CloseSnapshot(snapshot1)
|
||||
return nil, gas, nil
|
||||
}
|
||||
evm.StateDB.CreateAccount(addr)
|
||||
|
|
@ -286,6 +288,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
evm.Context.Transfer(evm.StateDB, caller, addr, value, &evm.chainRules)
|
||||
}
|
||||
|
||||
// Second snapshot: callee execution frame.
|
||||
snapshot2 := evm.StateDB.Snapshot()
|
||||
|
||||
if isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
|
||||
} else {
|
||||
|
|
@ -306,16 +311,31 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
// above we revert to the snapshot and consume any gas remaining. Additionally,
|
||||
// when we're in homestead this also counts for code storage gas errors.
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
// TODO: consider clearing up unused snapshots:
|
||||
//} else {
|
||||
// evm.StateDB.DiscardSnapshot(snapshot)
|
||||
} else {
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
// Charge callee's state changes to the callee's gas.
|
||||
bytesCharged := evm.StateDB.StateChangedBytes(snapshot2, false)
|
||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||
if !gas.CanAfford(stateGasCost) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
gas.Exhaust()
|
||||
return ret, gas, ErrOutOfGas
|
||||
}
|
||||
gas.Charge(stateGasCost)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot2)
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
// Cache parents costs (excluding subcalls)
|
||||
evm.StateDB.StateChangedBytes(snapshot1, true)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot1)
|
||||
}
|
||||
return ret, gas, err
|
||||
}
|
||||
|
|
@ -367,6 +387,17 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
|||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
} else {
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
bytesCharged := evm.StateDB.StateChangedBytes(snapshot, false)
|
||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||
if !gas.CanAfford(stateGasCost) {
|
||||
gas.Exhaust()
|
||||
return ret, gas, ErrOutOfGas
|
||||
}
|
||||
gas.Charge(stateGasCost)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
}
|
||||
return ret, gas, err
|
||||
}
|
||||
|
|
@ -411,7 +442,19 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
|
|||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
} else {
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
bytesCharged := evm.StateDB.StateChangedBytes(snapshot, false)
|
||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||
if !gas.CanAfford(stateGasCost) {
|
||||
gas.Exhaust()
|
||||
return ret, gas, ErrOutOfGas
|
||||
}
|
||||
gas.Charge(stateGasCost)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
}
|
||||
|
||||
return ret, gas, err
|
||||
}
|
||||
|
||||
|
|
@ -466,6 +509,8 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
|||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
} else {
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
}
|
||||
return ret, gas, err
|
||||
}
|
||||
|
|
@ -528,7 +573,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
// Create a new account on the state only if the object was not present.
|
||||
// It might be possible the contract code is deployed to a pre-existent
|
||||
// account with non-zero balance.
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
snapshot1 := evm.StateDB.Snapshot()
|
||||
if !evm.StateDB.Exist(address) {
|
||||
evm.StateDB.CreateAccount(address)
|
||||
}
|
||||
|
|
@ -555,6 +600,9 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
}
|
||||
evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules)
|
||||
|
||||
// Second snapshot: initcode execution frame.
|
||||
snapshot2 := evm.StateDB.Snapshot()
|
||||
|
||||
// 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.
|
||||
contract := NewContract(caller, address, value, gas, evm.jumpDests)
|
||||
|
|
@ -566,10 +614,29 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
|
||||
ret, err = evm.initNewContract(contract, address)
|
||||
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
// Revert to snapshot1 to undo both account creation and initcode changes.
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
if err != ErrExecutionReverted {
|
||||
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(snapshot2, false)
|
||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||
if !contract.Gas.CanAfford(stateGasCost) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
contract.Gas.Exhaust()
|
||||
return ret, address, contract.Gas, ErrOutOfGas
|
||||
}
|
||||
contract.Gas.Charge(stateGasCost)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot2)
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
// Cache snapshot1's state bytes (exclude subcalls)
|
||||
evm.StateDB.StateChangedBytes(snapshot1, true)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot1)
|
||||
}
|
||||
return ret, address, contract.Gas, err
|
||||
}
|
||||
|
|
@ -593,7 +660,15 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
|
|||
}
|
||||
|
||||
if !evm.chainRules.IsEIP4762 {
|
||||
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
||||
var createDataGas uint64
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
// EIP-8037: regular gas portion is the keccak hashing cost
|
||||
// (6 × ⌈L/32⌉). The state-gas portion (L × CPSB) is charged
|
||||
// at frame end via the journal's codeChange walker.
|
||||
createDataGas = ((uint64(len(ret)) + 31) / 32) * params.Keccak256WordGas
|
||||
} else {
|
||||
createDataGas = uint64(len(ret)) * params.CreateDataGas
|
||||
}
|
||||
if !contract.UseGas(GasCosts{RegularGas: createDataGas}, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||
return ret, ErrCodeStoreOutOfGas
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ func TestEIP2200(t *testing.T) {
|
|||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
||||
}
|
||||
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))
|
||||
if !errors.Is(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)
|
||||
initialGas := NewGasBudget(uint64(testGas))
|
||||
initialGas := NewGasBudget(uint64(testGas), 0)
|
||||
ret, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int))
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -18,17 +18,29 @@ package vm
|
|||
|
||||
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
|
||||
// multidimensional metering paradigm. It represents the cost
|
||||
// charged by an individual operation.
|
||||
type GasCosts struct {
|
||||
RegularGas uint64
|
||||
StateGas uint64
|
||||
StateGas int64
|
||||
}
|
||||
|
||||
// Sum returns the total gas (regular + state).
|
||||
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.
|
||||
|
|
@ -43,23 +55,30 @@ func (g GasCosts) String() string {
|
|||
type GasBudget struct {
|
||||
RegularGas uint64 // The leftover gas for execution and state gas usage
|
||||
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.
|
||||
func NewGasBudget(gas uint64) GasBudget {
|
||||
// NewGasBudgetReg creates a GasBudget with the given initial regular gas allowance.
|
||||
func NewGasBudgetReg(gas uint64) GasBudget {
|
||||
return GasBudget{RegularGas: gas}
|
||||
}
|
||||
|
||||
// Used returns the amount of regular gas consumed so far.
|
||||
func (g GasBudget) Used(initial GasBudget) uint64 {
|
||||
return initial.RegularGas - g.RegularGas
|
||||
// NewGasBudget creates a GasBudget with the given regular and state gas allowances.
|
||||
func NewGasBudget(regular, state uint64) GasBudget {
|
||||
return GasBudget{RegularGas: regular, StateGas: state}
|
||||
}
|
||||
|
||||
// Exhaust sets all remaining gas to zero, preserving the initial amount
|
||||
// for usage tracking.
|
||||
// Used returns the total amount of gas consumed so far (regular + state).
|
||||
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() {
|
||||
g.RegularGas = 0
|
||||
g.StateGas = 0
|
||||
}
|
||||
|
||||
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.
|
||||
// When state gas exceeds the reservoir, the excess spills to regular gas.
|
||||
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
|
||||
// pre-charge gas value and false if the budget does not have sufficient
|
||||
// gas to cover the cost.
|
||||
// pre-charge regular gas value and false if the budget does not have
|
||||
// sufficient gas to cover the cost.
|
||||
func (g *GasBudget) Charge(cost GasCosts) (uint64, bool) {
|
||||
prior := g.RegularGas
|
||||
if prior < cost.RegularGas {
|
||||
if !g.CanAfford(cost) {
|
||||
return prior, false
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (g *GasBudget) Refund(other GasBudget) (uint64, bool) {
|
||||
prior := g.RegularGas
|
||||
g.RegularGas += other.RegularGas
|
||||
return prior, g.RegularGas != prior
|
||||
g.StateGas += other.StateGas
|
||||
return prior, other.RegularGas != 0 || other.StateGas != 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
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
|
||||
// homestead we must check for CodeStoreOutOfGasError (homestead only
|
||||
// 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)
|
||||
// reuse size int for stackvalue
|
||||
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)
|
||||
// Push item on the stack based on the returned error.
|
||||
if suberr != nil {
|
||||
|
|
@ -747,7 +747,7 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
if !value.IsZero() {
|
||||
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 {
|
||||
temp.Clear()
|
||||
|
|
@ -781,7 +781,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
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 {
|
||||
temp.Clear()
|
||||
} else {
|
||||
|
|
@ -810,7 +810,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
// Get arguments from the memory.
|
||||
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 {
|
||||
temp.Clear()
|
||||
} else {
|
||||
|
|
@ -839,7 +839,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
// Get arguments from the memory.
|
||||
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 {
|
||||
temp.Clear()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,12 @@ type StateDB interface {
|
|||
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
||||
|
||||
RevertToSnapshot(int)
|
||||
|
||||
// CloseSnapshot marks the given snapshot's call frame as completed without
|
||||
// reverting any state. The call frame's entry range is recorded on the
|
||||
// parent frame so the parent can later iterate its own entries while
|
||||
// skipping over closed children. Snapshots must be closed in LIFO order.
|
||||
CloseSnapshot(int)
|
||||
Snapshot() int
|
||||
|
||||
AddLog(*types.Log)
|
||||
|
|
@ -99,4 +105,15 @@ type StateDB interface {
|
|||
|
||||
// Finalise must be invoked at the end of a transaction
|
||||
Finalise(bool) (*bal.StateAccessList, *bal.StateMutations)
|
||||
|
||||
// StateChangedBytes returns the number of state bytes created by the
|
||||
// call frame identified by the given snapshot ID. When excludeSubcalls
|
||||
// is true, cached subcall costs are not added to the total. Used by
|
||||
// EIP-8037 for state gas metering.
|
||||
StateChangedBytes(revid int, excludeSubcalls bool) int64
|
||||
|
||||
// SelfDestructRefundBytes returns the total state bytes to refund at
|
||||
// tx-end for accounts that were both created and selfdestructed during
|
||||
// this transaction (per EIP-6780).
|
||||
SelfDestructRefundBytes() int64
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func TestLoopInterrupt(t *testing.T) {
|
|||
timeout := make(chan bool)
|
||||
|
||||
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
|
||||
}(evm)
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ func BenchmarkInterpreter(b *testing.B) {
|
|||
value = uint256.NewInt(0)
|
||||
stack = newstack()
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ func newAmsterdamInstructionSet() JumpTable {
|
|||
instructionSet := newOsakaInstructionSet()
|
||||
enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
|
||||
enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
|
||||
enable8037(&instructionSet) // EIP-8037 SSTORE repricing
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -222,8 +222,80 @@ var (
|
|||
// gasSStoreEIP3529 implements gas cost for SSTORE according to EIP-3529
|
||||
// Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800)
|
||||
gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529)
|
||||
|
||||
// gasSStoreEIP8037 implements gas cost for SSTORE under EIP-8037.
|
||||
// New slot creation (orig=0, current=0, value!=0) is repriced from
|
||||
// SstoreSetGas (20,000) to SstoreUpdateGas - ColdSloadCost (2,900); the
|
||||
// state-gas portion (32 × CPSB) is charged at frame-end via the journal.
|
||||
// Likewise the same-tx 0→X→0 reset refund is reduced from 19,900 to
|
||||
// SstoreUpdateGas - ColdSloadCost - WarmStorageReadCost (2,800); the
|
||||
// state-gas refund is also handled at frame-end.
|
||||
gasSStoreEIP8037 = makeGasSStoreFuncAmsterdam(params.SstoreClearsScheduleRefundEIP3529)
|
||||
)
|
||||
|
||||
// makeGasSStoreFuncAmsterdam returns the EIP-8037 SSTORE gas function. It is
|
||||
// identical to makeGasSStoreFunc except that the regular-gas portion of new
|
||||
// slot creation and same-tx 0→X→0 reset is reduced (the state-gas portion is
|
||||
// charged/refunded at frame-end via the journal).
|
||||
func makeGasSStoreFuncAmsterdam(clearingRefund uint64) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 {
|
||||
return GasCosts{}, errors.New("not enough gas for reentrancy sentry")
|
||||
}
|
||||
var (
|
||||
y, x = stack.Back(1), stack.peek()
|
||||
slot = common.Hash(x.Bytes32())
|
||||
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||
cost = uint64(0)
|
||||
)
|
||||
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||
cost = params.ColdSloadCostEIP2929
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
}
|
||||
value := common.Hash(y.Bytes32())
|
||||
|
||||
// EIP-8037: regular-gas portion of new slot creation is the storage
|
||||
// update cost minus cold sload (2,900). State-gas portion is at
|
||||
// frame-end.
|
||||
sstoreNewSlotRegularGas := params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929
|
||||
|
||||
if current == value { // noop
|
||||
return GasCosts{RegularGas: cost + params.WarmStorageReadCostEIP2929}, nil
|
||||
}
|
||||
if original == current {
|
||||
if original == (common.Hash{}) { // create slot (2.1.1)
|
||||
return GasCosts{RegularGas: cost + sstoreNewSlotRegularGas}, nil
|
||||
}
|
||||
if value == (common.Hash{}) { // delete pre-existing slot
|
||||
evm.StateDB.AddRefund(clearingRefund)
|
||||
}
|
||||
return GasCosts{RegularGas: cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929)}, nil
|
||||
}
|
||||
if original != (common.Hash{}) {
|
||||
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
|
||||
evm.StateDB.SubRefund(clearingRefund)
|
||||
} else if value == (common.Hash{}) { // delete dirty (2.2.1.2)
|
||||
evm.StateDB.AddRefund(clearingRefund)
|
||||
}
|
||||
}
|
||||
if original == value {
|
||||
if original == (common.Hash{}) { // 0→X→0: reset to original-zero
|
||||
// EIP-8037: regular-gas refund is reduced because the
|
||||
// original SET cost was already reduced to
|
||||
// sstoreNewSlotRegularGas. State-gas refund (32 × CPSB)
|
||||
// is applied at frame-end.
|
||||
evm.StateDB.AddRefund(sstoreNewSlotRegularGas - params.WarmStorageReadCostEIP2929)
|
||||
} else { // reset to original existing slot
|
||||
evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929)
|
||||
}
|
||||
}
|
||||
return GasCosts{RegularGas: cost + params.WarmStorageReadCostEIP2929}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-3529
|
||||
func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
|
||||
gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
|||
cfg.Origin,
|
||||
common.BytesToAddress([]byte("contract")),
|
||||
input,
|
||||
vm.NewGasBudget(cfg.GasLimit),
|
||||
vm.NewGasBudgetReg(cfg.GasLimit),
|
||||
uint256.MustFromBig(cfg.Value),
|
||||
)
|
||||
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(
|
||||
cfg.Origin,
|
||||
input,
|
||||
vm.NewGasBudget(cfg.GasLimit),
|
||||
vm.NewGasBudgetReg(cfg.GasLimit),
|
||||
uint256.MustFromBig(cfg.Value),
|
||||
)
|
||||
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,
|
||||
address,
|
||||
input,
|
||||
vm.NewGasBudget(cfg.GasLimit),
|
||||
vm.NewGasBudgetReg(cfg.GasLimit),
|
||||
uint256.MustFromBig(cfg.Value),
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||
|
|
|
|||
|
|
@ -2111,3 +2111,116 @@ func runGetBlobs(t testing.TB, getBlobs getBlobsFn, start, limit int, fillRandom
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ func makeTestBAL(minSize int) *bal.BlockAccessList {
|
|||
Address: common.HexToAddress("0x01"),
|
||||
StorageReads: make([]*bal.EncodedStorage, n),
|
||||
}
|
||||
// Use a full-width 32-byte value (top byte 0xff) so each slot still
|
||||
// encodes to 33 RLP bytes regardless of the index.
|
||||
for i := range access.StorageReads {
|
||||
var slot common.Hash
|
||||
binary.BigEndian.PutUint64(slot[24:], uint64(i))
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
|
|||
gasLimit uint64 = 31000
|
||||
startGas uint64 = 10000
|
||||
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)
|
||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func TestStoreCapture(t *testing.T) {
|
|||
var (
|
||||
logger = NewStructLogger(nil)
|
||||
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)}
|
||||
var index common.Hash
|
||||
|
|
|
|||
|
|
@ -1347,6 +1347,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
|||
}
|
||||
|
||||
// Prevent redundant operations if args contain more authorizations than EVM may handle
|
||||
// TODO change with EIP-8037
|
||||
maxAuthorizations := uint64(*args.Gas) / params.CallNewAccountGas
|
||||
if uint64(len(args.AuthorizationList)) > maxAuthorizations {
|
||||
return nil, 0, nil, errors.New("insufficient gas to process all authorizations")
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
|
||||
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
|
||||
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.
|
||||
|
|
@ -100,6 +101,7 @@ const (
|
|||
TxAccessListAddressGas uint64 = 2400 // Per address 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
|
||||
TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037
|
||||
|
||||
// These have been changed during the course of the chain
|
||||
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
|||
b.StartTimer()
|
||||
start := time.Now()
|
||||
|
||||
initialGas := vm.NewGasBudget(msg.GasLimit)
|
||||
initialGas := vm.NewGasBudget(msg.GasLimit, 0)
|
||||
|
||||
// Execute the message.
|
||||
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), uint256.MustFromBig(msg.Value))
|
||||
|
|
|
|||
|
|
@ -81,7 +81,11 @@ func (tt *TransactionTest) Run() error {
|
|||
return
|
||||
}
|
||||
// Intrinsic gas
|
||||
<<<<<<< HEAD
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules)
|
||||
=======
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, 1)
|
||||
>>>>>>> eip-8037-rewrite
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -583,6 +583,18 @@ func (s *StateSetWithOrigin) decode(r *rlp.Stream) error {
|
|||
}
|
||||
}
|
||||
s.storageOrigin = storageSet
|
||||
|
||||
// Compute the size of origin data, keeping consistent with NewStateSetWithOrigin
|
||||
var size int
|
||||
for _, data := range s.accountOrigin {
|
||||
size += common.HashLength + len(data)
|
||||
}
|
||||
for _, slots := range s.storageOrigin {
|
||||
for _, data := range slots {
|
||||
size += 2*common.HashLength + len(data)
|
||||
}
|
||||
}
|
||||
s.size = s.stateSet.size + uint64(size)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue