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 <fjl@twurst.com>
Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
Janko Simonovic 2022-09-27 19:23:53 +02:00 committed by GitHub
parent 80cc34ac3c
commit 1a63a76fcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 32 deletions

View File

@ -19,6 +19,7 @@ package gethclient
import ( import (
"context" "context"
"encoding/json"
"math/big" "math/big"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
@ -118,15 +119,6 @@ func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []s
return &result, err 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 // CallContract executes a message call transaction, which is directly executed in the VM
// of the node, but never mined into the blockchain. // 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 var hex hexutil.Bytes
err := ec.c.CallContext( err := ec.c.CallContext(
ctx, &hex, "eth_call", toCallArg(msg), ctx, &hex, "eth_call", toCallArg(msg),
toBlockNumArg(blockNumber), toOverrideMap(overrides), toBlockNumArg(blockNumber), overrides,
) )
return hex, err return hex, err
} }
@ -218,26 +210,48 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
return arg return arg
} }
func toOverrideMap(overrides *map[common.Address]OverrideAccount) interface{} { // OverrideAccount specifies the state of an account to be overridden.
if overrides == nil { type OverrideAccount struct {
return nil // Nonce sets nonce of the account. Note: the nonce override will only
} // be applied when it is set to a non-zero value.
type overrideAccount struct { Nonce uint64
Nonce hexutil.Uint64 `json:"nonce"`
Code hexutil.Bytes `json:"code"` // Code sets the contract code. The override will be applied
Balance *hexutil.Big `json:"balance"` // when the code is non-nil, i.e. setting empty code is possible
State map[common.Hash]common.Hash `json:"state"` // using an empty slice.
StateDiff map[common.Hash]common.Hash `json:"stateDiff"` Code []byte
}
result := make(map[common.Address]overrideAccount) // Balance sets the account balance.
for addr, override := range *overrides { Balance *big.Int
result[addr] = overrideAccount{
Nonce: hexutil.Uint64(override.Nonce), // State sets the complete storage. The override will be applied
Code: override.Code, // when the given map is non-nil. Using an empty map wipes the
Balance: (*hexutil.Big)(override.Balance), // entire contract storage during the call.
State: override.State, State map[common.Hash]common.Hash
StateDiff: override.StateDiff,
} // StateDiff allows overriding individual storage slots.
} StateDiff map[common.Hash]common.Hash
return &result }
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)
} }

View File

@ -19,6 +19,7 @@ package gethclient
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json"
"math/big" "math/big"
"testing" "testing"
@ -322,3 +323,53 @@ func testCallContract(t *testing.T, client *rpc.Client) {
t.Fatalf("unexpected error: %v", err) 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)
}
}