From 1a63a76fcc8c8055d3361f3d9c4b0a5bc9c0da6d Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Tue, 27 Sep 2022 19:23:53 +0200 Subject: [PATCH] ethclient/gethclient: fix bugs in override object encoding (#25616) This fixes a bug where contract code would be overridden to empty code ("0x") when the Code field of OverrideAccount was left nil. The change also cleans up the encoding of overrides to only send necessary fields, and improves documentation. Fixes #25615 Co-authored-by: Felix Lange Co-authored-by: Martin Holst Swende --- ethclient/gethclient/gethclient.go | 78 +++++++++++++++---------- ethclient/gethclient/gethclient_test.go | 51 ++++++++++++++++ 2 files changed, 97 insertions(+), 32 deletions(-) diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go index edc441bd1..e182911aa 100644 --- a/ethclient/gethclient/gethclient.go +++ b/ethclient/gethclient/gethclient.go @@ -19,6 +19,7 @@ package gethclient import ( "context" + "encoding/json" "math/big" "runtime" "runtime/debug" @@ -118,15 +119,6 @@ func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []s return &result, err } -// OverrideAccount specifies the state of an account to be overridden. -type OverrideAccount struct { - Nonce uint64 `json:"nonce"` - Code []byte `json:"code"` - Balance *big.Int `json:"balance"` - State map[common.Hash]common.Hash `json:"state"` - StateDiff map[common.Hash]common.Hash `json:"stateDiff"` -} - // CallContract executes a message call transaction, which is directly executed in the VM // of the node, but never mined into the blockchain. // @@ -141,7 +133,7 @@ func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockN var hex hexutil.Bytes err := ec.c.CallContext( ctx, &hex, "eth_call", toCallArg(msg), - toBlockNumArg(blockNumber), toOverrideMap(overrides), + toBlockNumArg(blockNumber), overrides, ) return hex, err } @@ -218,26 +210,48 @@ func toCallArg(msg ethereum.CallMsg) interface{} { return arg } -func toOverrideMap(overrides *map[common.Address]OverrideAccount) interface{} { - if overrides == nil { - return nil - } - type overrideAccount struct { - Nonce hexutil.Uint64 `json:"nonce"` - Code hexutil.Bytes `json:"code"` - Balance *hexutil.Big `json:"balance"` - State map[common.Hash]common.Hash `json:"state"` - StateDiff map[common.Hash]common.Hash `json:"stateDiff"` - } - result := make(map[common.Address]overrideAccount) - for addr, override := range *overrides { - result[addr] = overrideAccount{ - Nonce: hexutil.Uint64(override.Nonce), - Code: override.Code, - Balance: (*hexutil.Big)(override.Balance), - State: override.State, - StateDiff: override.StateDiff, - } - } - return &result +// OverrideAccount specifies the state of an account to be overridden. +type OverrideAccount struct { + // Nonce sets nonce of the account. Note: the nonce override will only + // be applied when it is set to a non-zero value. + Nonce uint64 + + // Code sets the contract code. The override will be applied + // when the code is non-nil, i.e. setting empty code is possible + // using an empty slice. + Code []byte + + // Balance sets the account balance. + Balance *big.Int + + // State sets the complete storage. The override will be applied + // when the given map is non-nil. Using an empty map wipes the + // entire contract storage during the call. + State map[common.Hash]common.Hash + + // StateDiff allows overriding individual storage slots. + StateDiff 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"` + } + + output := acc{ + Nonce: hexutil.Uint64(a.Nonce), + Balance: (*hexutil.Big)(a.Balance), + StateDiff: a.StateDiff, + } + if a.Code != nil { + output.Code = hexutil.Encode(a.Code) + } + if a.State != nil { + output.State = a.State + } + return json.Marshal(output) } diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index e77c6015a..0e2f7e57b 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -19,6 +19,7 @@ package gethclient import ( "bytes" "context" + "encoding/json" "math/big" "testing" @@ -322,3 +323,53 @@ func testCallContract(t *testing.T, client *rpc.Client) { t.Fatalf("unexpected error: %v", err) } } + +func TestOverrideAccountMarshal(t *testing.T) { + om := map[common.Address]OverrideAccount{ + common.Address{0x11}: OverrideAccount{ + // Zero-valued nonce is not overriddden, but simply dropped by the encoder. + Nonce: 0, + }, + common.Address{0xaa}: OverrideAccount{ + Nonce: 5, + }, + common.Address{0xbb}: OverrideAccount{ + Code: []byte{1}, + }, + common.Address{0xcc}: OverrideAccount{ + // 'code', 'balance', 'state' should be set when input is + // a non-nil but empty value. + Code: []byte{}, + Balance: big.NewInt(0), + State: map[common.Hash]common.Hash{}, + // For 'stateDiff' the behavior is different, empty map + // is ignored because it makes no difference. + StateDiff: map[common.Hash]common.Hash{}, + }, + } + + marshalled, err := json.MarshalIndent(&om, "", " ") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expected := `{ + "0x1100000000000000000000000000000000000000": {}, + "0xaa00000000000000000000000000000000000000": { + "nonce": "0x5" + }, + "0xbb00000000000000000000000000000000000000": { + "code": "0x01" + }, + "0xcc00000000000000000000000000000000000000": { + "code": "0x", + "balance": "0x0", + "state": {} + } +}` + + if string(marshalled) != expected { + t.Error("wrong output:", string(marshalled)) + t.Error("want:", expected) + } +}