Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 13 additions & 14 deletions fvm/evm/emulator/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ func (proc *procedure) discardIfInvalid(res *types.Result) (bool, error) {

// commit commits the changes to the state (with optional finalization)
func (proc *procedure) commit(finalize bool) (hash.Hash, *gethBAL.ConstructionBlockAccessList, error) {
bal := proc.state.Finalise(true)
bal := proc.state.Finalise(proc.config.ChainRules())
stateUpdateCommitment, err := proc.state.Commit(finalize)
if err != nil {
// if known types (state errors) don't do anything and return
Expand Down Expand Up @@ -671,7 +671,7 @@ func (proc *procedure) deployAt(
gethTracing.NonceChangeContractCreator,
)

// After Amsterdam we limit the regular gas to 16M, the state gas to the transaction limit
// After Amsterdam we limit the execution gas to 16M, the state gas to the transaction limit
limit := call.GasLimit
if rules.IsAmsterdam {
limit = min(call.GasLimit, gethParams.MaxTxGas)
Expand Down Expand Up @@ -898,31 +898,30 @@ func (proc *procedure) initNewContract(
return call.GasLimit, gethVM.ErrInvalidCode
}

var gasConsumed uint64

rules := proc.config.ChainRules()
var gasConsumed uint64
if rules.IsAmsterdam {
// check max code size BEFORE charging gas so over-max code
// does not consume state gas (which would inflate tx_state).
// check whether the max code size has been exceeded
if err := gethVM.CheckMaxCodeSize(&rules, uint64(len(ret))); err != nil {
return call.GasLimit, gethVM.ErrMaxCodeSizeExceeded
}
// charge regular gas (hash cost) before state gas.
regularCost := toWordSize(uint64(len(ret))) * gethParams.Keccak256WordGas
if !chargeRegular(contract, regularCost, proc.evm.Config.Tracer, gethTracing.GasChangeCallCodeStorage) {
// charge execution gas (hash cost) before state gas.
executionCost := toWordSize(uint64(len(ret))) * gethParams.Keccak256WordGas
if !chargeExecution(contract, executionCost, proc.evm.Config.Tracer, gethTracing.GasChangeCallCodeStorage) {
return call.GasLimit, gethVM.ErrCodeStoreOutOfGas
}
// charge state gas (code-deposit) afterwards.
stateCost := uint64(len(ret)) * proc.evm.Context.CostPerStateByte
if !chargeState(contract, stateCost, proc.evm.Config.Tracer, gethTracing.GasChangeCallCodeStorage) {
return call.GasLimit, gethVM.ErrCodeStoreOutOfGas
}
gasConsumed = regularCost + stateCost
gasConsumed = executionCost + stateCost
} else {
// update gas usage
createDataCost := uint64(len(ret)) * gethParams.CreateDataGas
if !chargeRegular(contract, createDataCost, proc.evm.Config.Tracer, gethTracing.GasChangeCallCodeStorage) {
if !chargeExecution(contract, createDataCost, proc.evm.Config.Tracer, gethTracing.GasChangeCallCodeStorage) {
return call.GasLimit, gethVM.ErrCodeStoreOutOfGas
}
if err := gethVM.CheckMaxCodeSize(&rules, uint64(len(ret))); err != nil {
Expand Down Expand Up @@ -951,15 +950,15 @@ func checkAndConvertValue(input *big.Int) (converted *uint256.Int, isValid bool)
return value, true
}

// chargeRegular deducts regular gas only, with tracer integration.
// Returns false on OOG. Delegates the arithmetic to GasBudget.ChargeRegular.
func chargeRegular(
// chargeExecution deducts execution gas only, with tracer integration.
// Returns false on OOG. Delegates the arithmetic to GasBudget.ChargeExecution.
func chargeExecution(
c *gethVM.Contract,
r uint64,
logger *gethTracing.Hooks,
reason gethTracing.GasChangeReason,
) bool {
prior, ok := c.Gas.ChargeRegular(r)
prior, ok := c.Gas.ChargeExecution(r)
if !ok {
return false
}
Expand All @@ -969,7 +968,7 @@ func chargeRegular(
return true
}

// chargeState deducts state gas (spilling into regular when the reservoir is
// chargeState deducts state gas (spilling into execution when the reservoir is
// exhausted), with tracer integration. Returns false on OOG.
func chargeState(
c *gethVM.Contract,
Expand Down
2 changes: 1 addition & 1 deletion fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ func TestContractInteraction(t *testing.T) {

ret := new(big.Int).SetBytes(res.ReturnedData)
require.Equal(t, num, ret)
require.GreaterOrEqual(t, res.GasConsumed, uint64(18_420))
require.GreaterOrEqual(t, res.GasConsumed, uint64(17_520))
})
})

Expand Down
8 changes: 4 additions & 4 deletions fvm/evm/emulator/state/stateDB.go
Original file line number Diff line number Diff line change
Expand Up @@ -638,8 +638,8 @@ func (db *StateDB) Commit(finalize bool) (hash.Hash, error) {
// This is a no-op for our custom implementation of the StateDB interface,
// since Commit() already handles finalization and deletion of empty
// objects. But it still produces a valid BAL, under Amsterdam.
func (db *StateDB) Finalise(deleteEmptyObjects bool) *gethBAL.ConstructionBlockAccessList {
if db.stateAccessList == nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why removing this short-circuiting? is it worth to add back?

@m-Peter m-Peter Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we have the chain rules as a parameter, so we can deduce from there whether we are on Amsterdam or not.

Checking whether db.stateAccessList is nil or not is no longer a safe condition for deducing if we are running under Amsterdam or not:

// The access list built during this scope has been handed off to the caller,
// which merges it into the block-level list by adopting the account objects
// rather than copying them.
//
// Dereferencing the accessList explicitly, avoiding any following mutations
// affecting the external BAL.
s.stateAccessList = nil

This is what Geth does now: https://github.com/ethereum/go-ethereum/blob/master/core/state/statedb.go#L1146C2-L1152C25 .

The short-circuit return is still there though, just with a different condition:

if !rules.IsAmsterdam {
	return nil
}

func (db *StateDB) Finalise(rules gethParams.Rules) *gethBAL.ConstructionBlockAccessList {
if !rules.IsAmsterdam {
return nil
}

Expand All @@ -655,7 +655,7 @@ func (db *StateDB) Finalise(deleteEmptyObjects bool) *gethBAL.ConstructionBlockA

for slot, value := range dirtySlots {
address := slot.Address
if db.HasSelfDestructed(address) || (deleteEmptyObjects && db.Empty(address)) {
if db.HasSelfDestructed(address) || (rules.IsEIP158 && db.Empty(address)) {
continue
}
// Aggregate storage writes into the block-level access list.
Expand All @@ -667,7 +667,7 @@ func (db *StateDB) Finalise(deleteEmptyObjects bool) *gethBAL.ConstructionBlockA
}

for addr := range dirtyAddresses {
if db.HasSelfDestructed(addr) || (deleteEmptyObjects && db.Empty(addr)) {
if db.HasSelfDestructed(addr) || (rules.IsEIP158 && db.Empty(addr)) {
// Aggregate the account mutation into the block-level accessList
// if Amsterdam has been activated.
if db.stateAccessList != nil {
Expand Down
22 changes: 11 additions & 11 deletions fvm/evm/emulator/state/stateDB_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func TestStateDB(t *testing.T) {
require.True(t, db.AddressInAccessList(sender))
require.True(t, db.AddressInAccessList(coinbase))
require.True(t, db.AddressInAccessList(dest))
require.Nil(t, db.Finalise(true)) // no BAL unless Amsterdam is activated
require.Nil(t, db.Finalise(rules)) // no BAL unless Amsterdam is activated

for _, add := range precompiles {
require.True(t, db.AddressInAccessList(add))
Expand All @@ -280,7 +280,7 @@ func TestStateDB(t *testing.T) {
require.NoError(t, err)
db.Prepare(rules, sender, coinbase, &dest, precompiles, txAccesses)

require.NotNil(t, db.Finalise(true)) // BAL should be present when Amsterdam is activated
require.NotNil(t, db.Finalise(rules)) // BAL should be present when Amsterdam is activated
})

t.Run("test non-fatal error handling", func(t *testing.T) {
Expand Down Expand Up @@ -519,7 +519,7 @@ func TestStateDB(t *testing.T) {
require.NoError(t, err)

// Block access list should be empty initially
bal := db.Finalise(true)
bal := db.Finalise(rules)
require.Len(t, bal.Accounts, 0)

// Block access list with balance change on EOA
Expand All @@ -528,7 +528,7 @@ func TestStateDB(t *testing.T) {
db.AddBalance(addr1, balance, gethTracing.BalanceChangeUnspecified)
require.NoError(t, db.Error())

bal = db.Finalise(true)
bal = db.Finalise(rules)
require.Len(t, bal.Accounts, 1)
require.Equal(t, balance, bal.Accounts[addr1].BalanceChanges[0])

Expand All @@ -538,7 +538,7 @@ func TestStateDB(t *testing.T) {
db.SetNonce(addr2, nonce, gethTracing.NonceChangeContractCreator)
require.NoError(t, db.Error())

bal = db.Finalise(true)
bal = db.Finalise(rules)
require.Len(t, bal.Accounts, 2)
require.Equal(t, nonce, bal.Accounts[addr2].NonceChanges[0])

Expand All @@ -548,7 +548,7 @@ func TestStateDB(t *testing.T) {
db.SetCode(addr3, code, gethTracing.CodeChangeContractCreation)
require.NoError(t, db.Error())

bal = db.Finalise(true)
bal = db.Finalise(rules)
require.Len(t, bal.Accounts, 3)
require.Equal(t, code, bal.Accounts[addr3].CodeChange[0])

Expand Down Expand Up @@ -576,15 +576,15 @@ func TestStateDB(t *testing.T) {
db.SelfDestruct(addr4)
require.NoError(t, db.Error())

bal = db.Finalise(true)
bal = db.Finalise(rules)
require.Len(t, bal.Accounts, 1)
require.Equal(t, uint256.NewInt(0), bal.Accounts[addr4].BalanceChanges[0])

// Block access list with account read
addr5 := testutils.RandomCommonAddress(t)
db.GetBalance(addr5)

bal = db.Finalise(true)
bal = db.Finalise(rules)
require.Len(t, bal.Accounts, 2)
require.NotNil(t, bal.Accounts[addr5])

Expand All @@ -607,7 +607,7 @@ func TestStateDB(t *testing.T) {
db.GetCommittedState(addr7, key1)
require.NoError(t, db.Error())

bal = db.Finalise(true)
bal = db.Finalise(rules)
require.Len(t, bal.Accounts, 1)
require.Contains(t, bal.Accounts[addr7].StorageReads, key1)

Expand All @@ -630,7 +630,7 @@ func TestStateDB(t *testing.T) {
db.GetState(addr8, key1)
require.NoError(t, db.Error())

bal = db.Finalise(true)
bal = db.Finalise(rules)
require.Len(t, bal.Accounts, 1)
require.NotContains(t, bal.Accounts[addr8].StorageReads, key1)
require.Equal(t, value1, bal.Accounts[addr8].StorageWrites[key1][0])
Expand Down Expand Up @@ -663,7 +663,7 @@ func TestStateDB(t *testing.T) {
require.NoError(t, db.Error())
require.Equal(t, value1, ret)

bal = db.Finalise(true)
bal = db.Finalise(rules)
require.Len(t, bal.Accounts, 1)
require.Contains(t, bal.Accounts[addr9].StorageReads, key1)
})
Expand Down
24 changes: 12 additions & 12 deletions fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func TestEVMRun(t *testing.T) {
snapshot)

require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(331_205), blockEventPayload.TotalGasUsed)
require.Equal(t, uint64(330_305), blockEventPayload.TotalGasUsed)
require.NotEmpty(t, blockEventPayload.Hash)

txHashes := types.TransactionHashes{txEventPayload.Hash, feeTranferEventPayload.Hash}
Expand Down Expand Up @@ -209,8 +209,8 @@ func TestEVMRun(t *testing.T) {
require.Equal(t, types.ErrCodeNoError, res.ErrorCode)
require.Empty(t, res.ErrorMessage)
require.Nil(t, res.DeployedContractAddress)
require.Equal(t, uint64(18_420), res.GasConsumed)
require.Equal(t, uint64(18_420), res.MaxGasConsumed)
require.Equal(t, uint64(17_520), res.GasConsumed)
require.Equal(t, uint64(17_520), res.MaxGasConsumed)
require.Equal(t, num, new(big.Int).SetBytes(res.ReturnedData).Int64())
})
})
Expand Down Expand Up @@ -298,7 +298,7 @@ func TestEVMRun(t *testing.T) {
)

require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(126_605), blockEventPayload.TotalGasUsed)
require.Equal(t, uint64(125_705), blockEventPayload.TotalGasUsed)
require.NotEmpty(t, blockEventPayload.Hash)

require.Equal(t, uint16(types.ErrCodeNoError), txEventPayload.ErrorCode)
Expand Down Expand Up @@ -1344,7 +1344,7 @@ func TestEVMRun(t *testing.T) {
blockEventPayload, _ := callEVMHeartBeat(t, ctx, vm, snapshot)

require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(331_205), blockEventPayload.TotalGasUsed)
require.Equal(t, uint64(330_305), blockEventPayload.TotalGasUsed)
require.NotEmpty(t, blockEventPayload.Hash)

txHashes := types.TransactionHashes{txEventPayload.Hash, feeTranferEventPayload.Hash}
Expand Down Expand Up @@ -1454,7 +1454,7 @@ func TestEVMRun(t *testing.T) {
)

require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(331_205), blockEventPayload.TotalGasUsed)
require.Equal(t, uint64(330_305), blockEventPayload.TotalGasUsed)
require.NotEmpty(t, blockEventPayload.Hash)

txHashes := types.TransactionHashes{txEventPayload.Hash, feeTranferEventPayload.Hash}
Expand Down Expand Up @@ -1613,7 +1613,7 @@ func TestEVMBatchRun(t *testing.T) {
snapshot)

require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(443_733), blockEventPayload.TotalGasUsed)
require.Equal(t, uint64(439_233), blockEventPayload.TotalGasUsed)
require.Equal(t,
txHashes.RootHash(),
blockEventPayload.TransactionHashRoot,
Expand Down Expand Up @@ -4313,7 +4313,7 @@ func TestDryRun(t *testing.T) {

require.NoError(t, err)
require.NoError(t, output.Err)
assert.Equal(t, uint64(440), output.ComputationUsed)
assert.Equal(t, uint64(439), output.ComputationUsed)
},
)
})
Expand Down Expand Up @@ -4921,7 +4921,7 @@ func TestDryCall(t *testing.T) {

require.NoError(t, err)
require.NoError(t, output.Err)
assert.Equal(t, uint64(199), output.ComputationUsed)
assert.Equal(t, uint64(198), output.ComputationUsed)

// Increase call count of EVM.dryCall to 15
iterations = cadence.NewUInt(15)
Expand All @@ -4943,7 +4943,7 @@ func TestDryCall(t *testing.T) {

require.NoError(t, err)
require.NoError(t, output.Err)
assert.Equal(t, uint64(567), output.ComputationUsed)
assert.Equal(t, uint64(566), output.ComputationUsed)
},
)
})
Expand Down Expand Up @@ -7275,7 +7275,7 @@ func TestEthLogEmissionWithSelfDestruct(t *testing.T) {
require.NoError(t, err)
require.Equal(t, uint16(types.ErrCodeNoError), txEventPayload.ErrorCode)
require.Equal(t, uint16(0), txEventPayload.Index)
require.Equal(t, uint64(1_249_568), txEventPayload.GasConsumed)
require.Equal(t, uint64(1_250_668), txEventPayload.GasConsumed)
require.Greater(t, len(txEventPayload.Logs), 0)

gethLogs := []*gethTypes.Log{}
Expand Down Expand Up @@ -7371,7 +7371,7 @@ func TestEthLogEmissionWithSelfDestruct(t *testing.T) {
require.NoError(t, err)
require.Equal(t, uint16(types.ErrCodeNoError), txEventPayload.ErrorCode)
require.Equal(t, uint16(0), txEventPayload.Index)
require.Equal(t, uint64(1_249_589), txEventPayload.GasConsumed)
require.Equal(t, uint64(1_250_689), txEventPayload.GasConsumed)
require.Greater(t, len(txEventPayload.Logs), 0)

gethLogs := []*gethTypes.Log{}
Expand Down
16 changes: 8 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/dgraph-io/badger/v2 v2.2007.4
github.com/ef-ds/deque v1.0.4
github.com/ethereum/go-ethereum v1.17.5
github.com/ethereum/go-ethereum v1.17.6-0.20260908010751-7538039f0679
github.com/fxamacker/cbor/v2 v2.9.2-0.20260331174317-a78e92ec038e
github.com/gammazero/workerpool v1.1.3
github.com/gogo/protobuf v1.3.2
Expand Down Expand Up @@ -74,13 +74,13 @@ require (
go.opentelemetry.io/otel/trace v1.44.0
go.uber.org/atomic v1.11.0
go.uber.org/multierr v1.11.0
golang.org/x/crypto v0.54.0
golang.org/x/crypto v0.55.0
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
golang.org/x/sync v0.22.0
golang.org/x/sys v0.47.0
golang.org/x/text v0.40.0
golang.org/x/text v0.41.0
golang.org/x/time v0.14.0
golang.org/x/tools v0.47.0
golang.org/x/tools v0.49.0
google.golang.org/api v0.267.0
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409
google.golang.org/grpc v1.83.0
Expand Down Expand Up @@ -136,7 +136,7 @@ require (
github.com/pion/stun/v3 v3.1.5 // indirect
github.com/pion/transport/v4 v4.0.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 // indirect
golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 // indirect
)

require (
Expand Down Expand Up @@ -180,7 +180,7 @@ require (
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b // indirect
github.com/cockroachdb/swiss v0.0.0-20260820225851-333444432258 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/consensys/gnark-crypto v0.18.1 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
Expand Down Expand Up @@ -352,8 +352,8 @@ require (
go.uber.org/fx v1.23.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/mod v0.39.0 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/term v0.45.0 // indirect
gonum.org/v1/gonum v0.17.0 // indirect
Expand Down
Loading
Loading