From df3d0912c23da4d306dd1de4305222778fa4db3c Mon Sep 17 00:00:00 2001 From: allen Date: Fri, 19 Sep 2025 14:51:36 -0400 Subject: [PATCH 1/5] internal/ethapi: add transient storage override support for eth_call --- interfaces.go | 21 +++++---- internal/ethapi/api_test.go | 19 ++++++++ internal/ethapi/override/override.go | 9 +++- internal/ethapi/override/override_test.go | 55 +++++++++++++++++++++++ 4 files changed, 95 insertions(+), 9 deletions(-) diff --git a/interfaces.go b/interfaces.go index 21d42c6d34..345f59b094 100644 --- a/interfaces.go +++ b/interfaces.go @@ -317,21 +317,26 @@ type OverrideAccount struct { // StateDiff allows overriding individual storage slots. StateDiff map[common.Hash]common.Hash + + // TransientStorage allows overriding transient storage slots. + TransientStorage map[common.Hash]common.Hash } func (a OverrideAccount) MarshalJSON() ([]byte, error) { type acc struct { - Nonce hexutil.Uint64 `json:"nonce,omitempty"` - Code string `json:"code,omitempty"` - Balance *hexutil.Big `json:"balance,omitempty"` - State interface{} `json:"state,omitempty"` - StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"` + Nonce hexutil.Uint64 `json:"nonce,omitempty"` + Code string `json:"code,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + State interface{} `json:"state,omitempty"` + StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"` + TransientStorage map[common.Hash]common.Hash `json:"transientStorage,omitempty"` } output := acc{ - Nonce: hexutil.Uint64(a.Nonce), - Balance: (*hexutil.Big)(a.Balance), - StateDiff: a.StateDiff, + Nonce: hexutil.Uint64(a.Nonce), + Balance: (*hexutil.Big)(a.Balance), + StateDiff: a.StateDiff, + TransientStorage: a.TransientStorage, } if a.Code != nil { output.Code = hexutil.Encode(a.Code) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 62e9979d3d..f88923dbf3 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1291,6 +1291,25 @@ func TestCall(t *testing.T) { }, expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`), }, + // Test transient storage override + { + name: "transient storage override takes effect", + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[1].addr, + To: &randomAccounts[2].addr, + }, + overrides: override.StateOverride{ + randomAccounts[2].addr: override.OverrideAccount{ + // PUSH1 0x01 PUSH1 0x00 TSTORE PUSH1 0x00 TLOAD PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 RETURN + Code: hex2Bytes("0x600160005d60005c60005260206000f3"), + TransientStorage: map[common.Hash]common.Hash{ + common.Hash{}: common.HexToHash("0xabcd"), + }, + }, + }, + want: "0x000000000000000000000000000000000000000000000000000000000000abcd", + }, } for _, tc := range testSuite { result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) diff --git a/internal/ethapi/override/override.go b/internal/ethapi/override/override.go index 96ba77ab0a..47d01d7259 100644 --- a/internal/ethapi/override/override.go +++ b/internal/ethapi/override/override.go @@ -35,7 +35,7 @@ import ( // OverrideAccount indicates the overriding fields of account during the execution // of a message call. // Note, state and stateDiff can't be specified at the same time. If state is -// set, message execution will only use the data in the given state. Otherwise +// set, message execution will only use the data in the given state. Otherwise, // if stateDiff is set, all diff will be applied first and then execute the call // message. type OverrideAccount struct { @@ -44,6 +44,7 @@ type OverrideAccount struct { Balance *hexutil.Big `json:"balance"` State map[common.Hash]common.Hash `json:"state"` StateDiff map[common.Hash]common.Hash `json:"stateDiff"` + TransientStorage map[common.Hash]common.Hash `json:"transientStorage"` MovePrecompileTo *common.Address `json:"movePrecompileToAddress"` } @@ -117,6 +118,12 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi statedb.SetState(addr, key, value) } } + // Apply transient storage overrides. + if account.TransientStorage != nil { + for key, value := range account.TransientStorage { + statedb.SetTransientState(addr, key, value) + } + } } // Now finalize the changes. Finalize is normally performed between transactions. // By using finalize, the overrides are semantically behaving as diff --git a/internal/ethapi/override/override_test.go b/internal/ethapi/override/override_test.go index 6feafaac75..45a97af70f 100644 --- a/internal/ethapi/override/override_test.go +++ b/internal/ethapi/override/override_test.go @@ -128,3 +128,58 @@ func hex2Bytes(str string) *hexutil.Bytes { rpcBytes := hexutil.Bytes(common.FromHex(str)) return &rpcBytes } + +func TestStateOverrideTransientStorage(t *testing.T) { + db := state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil) + statedb, err := state.New(types.EmptyRootHash, db) + if err != nil { + t.Fatalf("failed to create statedb: %v", err) + } + + addr := common.BytesToAddress([]byte{0x1}) + key1 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001") + key2 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002") + value1 := common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") + value2 := common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222") + + // Verify initial state is empty + if got := statedb.GetTransientState(addr, key1); got != (common.Hash{}) { + t.Fatalf("expected initial transient state to be empty, got %s", got.Hex()) + } + if got := statedb.GetTransientState(addr, key2); got != (common.Hash{}) { + t.Fatalf("expected initial transient state to be empty, got %s", got.Hex()) + } + + // Apply override with transient storage + override := StateOverride{ + addr: OverrideAccount{ + TransientStorage: map[common.Hash]common.Hash{ + key1: value1, + key2: value2, + }, + }, + } + + if err := override.Apply(statedb, nil); err != nil { + t.Fatalf("failed to apply override: %v", err) + } + + // Verify transient storage was set + if got := statedb.GetTransientState(addr, key1); got != value1 { + t.Errorf("expected transient state for key1 to be %s, got %s", value1.Hex(), got.Hex()) + } + if got := statedb.GetTransientState(addr, key2); got != value2 { + t.Errorf("expected transient state for key2 to be %s, got %s", value2.Hex(), got.Hex()) + } + + // Verify other addresses/keys remain empty + otherAddr := common.BytesToAddress([]byte{0x2}) + if got := statedb.GetTransientState(otherAddr, key1); got != (common.Hash{}) { + t.Errorf("expected transient state for different address to be empty, got %s", got.Hex()) + } + + otherKey := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000003") + if got := statedb.GetTransientState(addr, otherKey); got != (common.Hash{}) { + t.Errorf("expected transient state for different key to be empty, got %s", got.Hex()) + } +} From 4b73f9e293e0c7854c1dc2402cd2a2330f2630cd Mon Sep 17 00:00:00 2001 From: allen Date: Fri, 19 Sep 2025 21:10:22 -0400 Subject: [PATCH 2/5] fix bug --- core/state/statedb.go | 18 ++++++++++++++++++ internal/ethapi/api_test.go | 4 ++-- internal/ethapi/override/override.go | 19 +++++++++++++------ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 769c8504c2..5d3db16014 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -129,6 +129,8 @@ type StateDB struct { // Transient storage transientStorage transientStorage + // Overrides to apply after Prepare() + pendingTransientOverrides map[common.Address]map[common.Hash]common.Hash // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. @@ -1465,6 +1467,22 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d } // Reset transient storage at the beginning of transaction execution s.transientStorage = newTransientStorage() + + // Apply any pending transient storage overrides after reset + if s.pendingTransientOverrides != nil { + for addr, storage := range s.pendingTransientOverrides { + for key, value := range storage { + s.transientStorage.Set(addr, key, value) + } + } + s.pendingTransientOverrides = nil // Clear after applying + } +} + +// SetPendingTransientOverrides stores transient storage overrides to be applied +// after the next Prepare() call. This ensures overrides are applied to a clean state. +func (s *StateDB) SetPendingTransientOverrides(overrides map[common.Address]map[common.Hash]common.Hash) { + s.pendingTransientOverrides = overrides } // AddAddressToAccessList adds the given address to the access list diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index f88923dbf3..1f39d4047b 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1301,8 +1301,8 @@ func TestCall(t *testing.T) { }, overrides: override.StateOverride{ randomAccounts[2].addr: override.OverrideAccount{ - // PUSH1 0x01 PUSH1 0x00 TSTORE PUSH1 0x00 TLOAD PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 RETURN - Code: hex2Bytes("0x600160005d60005c60005260206000f3"), + // PUSH1 0x00 TLOAD PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 RETURN + Code: hex2Bytes("0x60005c60005260206000f3"), TransientStorage: map[common.Hash]common.Hash{ common.Hash{}: common.HexToHash("0xabcd"), }, diff --git a/internal/ethapi/override/override.go b/internal/ethapi/override/override.go index 47d01d7259..21f30f540e 100644 --- a/internal/ethapi/override/override.go +++ b/internal/ethapi/override/override.go @@ -61,6 +61,19 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi if diff == nil { return nil } + // Get transient storage overrides to apply after Prepare() + transientOverrides := make(map[common.Address]map[common.Hash]common.Hash) + for addr, account := range *diff { + if account.TransientStorage != nil && len(account.TransientStorage) > 0 { + transientOverrides[addr] = account.TransientStorage + } + } + + // Store transient storage overrides + if len(transientOverrides) > 0 { + statedb.SetPendingTransientOverrides(transientOverrides) + } + // Iterate in deterministic order so error messages and behavior are stable (e.g. for tests). addrs := slices.SortedFunc(maps.Keys(*diff), common.Address.Cmp) @@ -118,12 +131,6 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi statedb.SetState(addr, key, value) } } - // Apply transient storage overrides. - if account.TransientStorage != nil { - for key, value := range account.TransientStorage { - statedb.SetTransientState(addr, key, value) - } - } } // Now finalize the changes. Finalize is normally performed between transactions. // By using finalize, the overrides are semantically behaving as From 277c2e5620d3ebac59b7e5049a82a9a3a1c50c5c Mon Sep 17 00:00:00 2001 From: allen Date: Fri, 19 Sep 2025 21:55:05 -0400 Subject: [PATCH 3/5] fix testcase --- internal/ethapi/override/override_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/ethapi/override/override_test.go b/internal/ethapi/override/override_test.go index 45a97af70f..7bfe179713 100644 --- a/internal/ethapi/override/override_test.go +++ b/internal/ethapi/override/override_test.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/triedb" ) @@ -164,6 +165,8 @@ func TestStateOverrideTransientStorage(t *testing.T) { t.Fatalf("failed to apply override: %v", err) } + statedb.Prepare(params.Rules{}, common.Address{}, common.Address{}, nil, nil, nil) + // Verify transient storage was set if got := statedb.GetTransientState(addr, key1); got != value1 { t.Errorf("expected transient state for key1 to be %s, got %s", value1.Hex(), got.Hex()) From 8c696939f207530c1a48c2b9a47f4e531bec3ac0 Mon Sep 17 00:00:00 2001 From: allen Date: Tue, 30 Sep 2025 10:28:48 -0400 Subject: [PATCH 4/5] add transient override param --- ethclient/gethclient/gethclient.go | 29 +++++++++++++++++++++++ graphql/graphql.go | 4 ++-- interfaces.go | 21 +++++++--------- internal/ethapi/api.go | 13 ++++++---- internal/ethapi/api_test.go | 25 ++++++++++--------- internal/ethapi/override/override.go | 26 ++++++++++---------- internal/ethapi/override/override_test.go | 16 +++++-------- 7 files changed, 79 insertions(+), 55 deletions(-) diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go index e677e2bb21..7d7109f27e 100644 --- a/ethclient/gethclient/gethclient.go +++ b/ethclient/gethclient/gethclient.go @@ -171,6 +171,31 @@ func (ec *Client) CallContractWithBlockOverrides(ctx context.Context, msg ethere return hex, err } +// CallContractWithTransientOverrides executes a message call transaction, which is directly executed +// in the VM of the node, but never mined into the blockchain. +// +// blockNumber selects the block height at which the call runs. It can be nil, in which +// case the code is taken from the latest known block. Note that state from very old +// blocks might not be available. +// +// overrides specifies a map of contract states that should be overwritten before executing +// the message call. +// +// blockOverrides specifies block fields exposed to the EVM that can be overridden for the call. +// +// transientOverrides specifies transient storage slots that should be overwritten before executing +// the message call. Transient storage is reset at the beginning of each transaction. +// +// Please use ethclient.CallContract instead if you don't need the override functionality. +func (ec *Client) CallContractWithTransientOverrides(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount, blockOverrides *BlockOverrides, transientOverrides *TransientOverrides) ([]byte, error) { + var hex hexutil.Bytes + err := ec.c.CallContext( + ctx, &hex, "eth_call", toCallArg(msg), + toBlockNumArg(blockNumber), overrides, blockOverrides, transientOverrides, + ) + return hex, err +} + // GCStats retrieves the current garbage collection stats from a geth node. func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) { var result debug.GCStats @@ -407,3 +432,7 @@ type OverrideAccount = ethereum.OverrideAccount // BlockOverrides is an alias for ethereum.BlockOverrides. type BlockOverrides = ethereum.BlockOverrides + +// TransientOverrides specifies transient storage slots to override for eth_call. +// The map key is the contract address, and the value is another map of slot to value. +type TransientOverrides map[common.Address]map[common.Hash]common.Hash diff --git a/graphql/graphql.go b/graphql/graphql.go index f25bfd127a..d6285fc132 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -1207,7 +1207,7 @@ func (c *CallResult) Status() hexutil.Uint64 { func (b *Block) Call(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (*CallResult, error) { - result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap()) + result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, nil, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap()) if err != nil { return nil, err } @@ -1270,7 +1270,7 @@ func (p *Pending) Call(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (*CallResult, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap()) + result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, nil, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap()) if err != nil { return nil, err } diff --git a/interfaces.go b/interfaces.go index 345f59b094..21d42c6d34 100644 --- a/interfaces.go +++ b/interfaces.go @@ -317,26 +317,21 @@ type OverrideAccount struct { // StateDiff allows overriding individual storage slots. StateDiff map[common.Hash]common.Hash - - // TransientStorage allows overriding transient storage slots. - TransientStorage map[common.Hash]common.Hash } func (a OverrideAccount) MarshalJSON() ([]byte, error) { type acc struct { - Nonce hexutil.Uint64 `json:"nonce,omitempty"` - Code string `json:"code,omitempty"` - Balance *hexutil.Big `json:"balance,omitempty"` - State interface{} `json:"state,omitempty"` - StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"` - TransientStorage map[common.Hash]common.Hash `json:"transientStorage,omitempty"` + Nonce hexutil.Uint64 `json:"nonce,omitempty"` + Code string `json:"code,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + State interface{} `json:"state,omitempty"` + StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"` } output := acc{ - Nonce: hexutil.Uint64(a.Nonce), - Balance: (*hexutil.Big)(a.Balance), - StateDiff: a.StateDiff, - TransientStorage: a.TransientStorage, + Nonce: hexutil.Uint64(a.Nonce), + Balance: (*hexutil.Big)(a.Balance), + StateDiff: a.StateDiff, } if a.Code != nil { output.Code = hexutil.Encode(a.Code) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 4f217d0578..24f61e9ec5 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -727,7 +727,7 @@ func (context *ChainContext) GetHeaderByHash(hash common.Hash) *types.Header { return header } -func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { +func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, transientOverrides *override.TransientStorageOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) if blockOverrides != nil { if err := blockOverrides.Apply(&blockCtx); err != nil { @@ -740,6 +740,9 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S return nil, err } + // Apply transient storage overrides. These will be applied after Prepare() is called. + transientOverrides.Apply(state) + // Setup context so it may be cancelled the call has completed // or, in case of unmetered gas, setup a context with a timeout. var cancel context.CancelFunc @@ -807,14 +810,14 @@ func applyMessageWithEVM(ctx context.Context, evm *vm.EVM, msg *core.Message, ti return result, nil } -func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { +func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, transientOverrides *override.TransientStorageOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } - return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap) + return doCall(ctx, b, args, state, header, overrides, blockOverrides, transientOverrides, timeout, globalGasCap) } // Call executes the given transaction on the state for the given block number. @@ -823,12 +826,12 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash // // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. -func (api *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *override.StateOverride, blockOverrides *override.BlockOverrides) (hexutil.Bytes, error) { +func (api *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, transientOverrides *override.TransientStorageOverride) (hexutil.Bytes, error) { if blockNrOrHash == nil { latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &latest } - result, err := DoCall(ctx, api.b, args, *blockNrOrHash, overrides, blockOverrides, api.b.RPCEVMTimeout(), api.b.RPCGasCap()) + result, err := DoCall(ctx, api.b, args, *blockNrOrHash, overrides, blockOverrides, transientOverrides, api.b.RPCEVMTimeout(), api.b.RPCGasCap()) if err != nil { return nil, err } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 1f39d4047b..a9dcc9a3ec 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1032,13 +1032,14 @@ func TestCall(t *testing.T) { })) randomAccounts := newAccounts(3) var testSuite = []struct { - name string - blockNumber rpc.BlockNumber - overrides override.StateOverride - call TransactionArgs - blockOverrides override.BlockOverrides - expectErr error - want string + name string + blockNumber rpc.BlockNumber + overrides override.StateOverride + call TransactionArgs + blockOverrides override.BlockOverrides + transientOverrides override.TransientStorageOverride + expectErr error + want string }{ // transfer on genesis { @@ -1303,16 +1304,18 @@ func TestCall(t *testing.T) { randomAccounts[2].addr: override.OverrideAccount{ // PUSH1 0x00 TLOAD PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 RETURN Code: hex2Bytes("0x60005c60005260206000f3"), - TransientStorage: map[common.Hash]common.Hash{ - common.Hash{}: common.HexToHash("0xabcd"), - }, + }, + }, + transientOverrides: override.TransientStorageOverride{ + randomAccounts[2].addr: map[common.Hash]common.Hash{ + common.Hash{}: common.HexToHash("0xabcd"), }, }, want: "0x000000000000000000000000000000000000000000000000000000000000abcd", }, } for _, tc := range testSuite { - result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) + result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides, &tc.transientOverrides) if tc.expectErr != nil { if err == nil { t.Errorf("test %s: want error %v, have nothing", tc.name, tc.expectErr) diff --git a/internal/ethapi/override/override.go b/internal/ethapi/override/override.go index 21f30f540e..2f288bc244 100644 --- a/internal/ethapi/override/override.go +++ b/internal/ethapi/override/override.go @@ -44,13 +44,15 @@ type OverrideAccount struct { Balance *hexutil.Big `json:"balance"` State map[common.Hash]common.Hash `json:"state"` StateDiff map[common.Hash]common.Hash `json:"stateDiff"` - TransientStorage map[common.Hash]common.Hash `json:"transientStorage"` MovePrecompileTo *common.Address `json:"movePrecompileToAddress"` } // StateOverride is the collection of overridden accounts. type StateOverride map[common.Address]OverrideAccount +// TransientStorageOverride is the collection of transient storage overrides. +type TransientStorageOverride map[common.Address]map[common.Hash]common.Hash + func (diff *StateOverride) has(address common.Address) bool { _, ok := (*diff)[address] return ok @@ -61,22 +63,10 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi if diff == nil { return nil } - // Get transient storage overrides to apply after Prepare() - transientOverrides := make(map[common.Address]map[common.Hash]common.Hash) - for addr, account := range *diff { - if account.TransientStorage != nil && len(account.TransientStorage) > 0 { - transientOverrides[addr] = account.TransientStorage - } - } - - // Store transient storage overrides - if len(transientOverrides) > 0 { - statedb.SetPendingTransientOverrides(transientOverrides) - } - // Iterate in deterministic order so error messages and behavior are stable (e.g. for tests). addrs := slices.SortedFunc(maps.Keys(*diff), common.Address.Cmp) + // Tracks destinations of precompiles that were moved. dirtyAddrs := make(map[common.Address]struct{}) for _, addr := range addrs { @@ -139,6 +129,14 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi return nil } +// Apply stores transient storage overrides to be applied after Prepare(). +func (diff *TransientStorageOverride) Apply(statedb *state.StateDB) { + if diff == nil || len(*diff) == 0 { + return + } + statedb.SetPendingTransientOverrides(*diff) +} + // BlockOverrides is a set of header fields to override. type BlockOverrides struct { Number *hexutil.Big diff --git a/internal/ethapi/override/override_test.go b/internal/ethapi/override/override_test.go index 7bfe179713..9d1ed898dc 100644 --- a/internal/ethapi/override/override_test.go +++ b/internal/ethapi/override/override_test.go @@ -151,19 +151,15 @@ func TestStateOverrideTransientStorage(t *testing.T) { t.Fatalf("expected initial transient state to be empty, got %s", got.Hex()) } - // Apply override with transient storage - override := StateOverride{ - addr: OverrideAccount{ - TransientStorage: map[common.Hash]common.Hash{ - key1: value1, - key2: value2, - }, + // Apply transient storage override + transientOverride := TransientStorageOverride{ + addr: map[common.Hash]common.Hash{ + key1: value1, + key2: value2, }, } - if err := override.Apply(statedb, nil); err != nil { - t.Fatalf("failed to apply override: %v", err) - } + transientOverride.Apply(statedb) statedb.Prepare(params.Rules{}, common.Address{}, common.Address{}, nil, nil, nil) From dcf422d3b43fcb312da2578f643df74da67b1854 Mon Sep 17 00:00:00 2001 From: frederick Date: Tue, 31 Mar 2026 22:51:35 -0400 Subject: [PATCH 5/5] pass ci --- internal/ethapi/override/override.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/ethapi/override/override.go b/internal/ethapi/override/override.go index 2f288bc244..1be688676b 100644 --- a/internal/ethapi/override/override.go +++ b/internal/ethapi/override/override.go @@ -66,7 +66,6 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi // Iterate in deterministic order so error messages and behavior are stable (e.g. for tests). addrs := slices.SortedFunc(maps.Keys(*diff), common.Address.Cmp) - // Tracks destinations of precompiles that were moved. dirtyAddrs := make(map[common.Address]struct{}) for _, addr := range addrs {