Skip to content

Commit

Permalink
ethclient, internal/ethapi: allow eth_call with overrides
Browse files Browse the repository at this point in the history
This publishes the previously internal ethapi.StateOverrides type as
well as adds the methods CallContractWithOverrides and
PendingCallContractWithOverrides to ethclient.Client.

#22197
  • Loading branch information
attente committed Jun 29, 2021
1 parent 4fcc93d commit 0be7ed6
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 23 deletions.
5 changes: 3 additions & 2 deletions eth/tracers/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"testing"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
Expand Down Expand Up @@ -345,7 +346,7 @@ func TestOverridenTraceCall(t *testing.T) {
config: &TraceCallConfig{
Tracer: &tracer,
StateOverrides: &ethapi.StateOverride{
randomAccounts[0].addr: ethapi.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
randomAccounts[0].addr: ethereum.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
},
},
expectErr: nil,
Expand Down Expand Up @@ -398,7 +399,7 @@ func TestOverridenTraceCall(t *testing.T) {
config: &TraceCallConfig{
Tracer: &tracer,
StateOverrides: &ethapi.StateOverride{
randomAccounts[2].addr: ethapi.OverrideAccount{
randomAccounts[2].addr: ethereum.OverrideAccount{
Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033")),
StateDiff: newStates([]common.Hash{{}}, []common.Hash{common.BigToHash(big.NewInt(123))}),
},
Expand Down
28 changes: 23 additions & 5 deletions ethclient/ethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,19 +473,37 @@ func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) {
// case the code is taken from the latest known block. Note that state from very old
// blocks might not be available.
func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
return ec.CallContractWithOverrides(ctx, msg, blockNumber, nil)
}

// PendingCallContract executes a message call transaction using the EVM.
// The state seen by the contract call is the pending state.
func (ec *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
return ec.PendingCallContractWithOverrides(ctx, msg, nil)
}

// CallContractWithOverrides 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 accounts whose state will be overridden prior to execution.
func (ec *Client) CallContractWithOverrides(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides ethereum.StateOverride) ([]byte, error) {
var hex hexutil.Bytes
err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), toBlockNumArg(blockNumber))
err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), toBlockNumArg(blockNumber), overrides)
if err != nil {
return nil, err
}
return hex, nil
}

// PendingCallContract executes a message call transaction using the EVM.
// The state seen by the contract call is the pending state.
func (ec *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
// PendingCallContractWithOverrides executes a message call transaction using the EVM.
// The state seen by the contract call is the pending state after account overrides have been applied.
func (ec *Client) PendingCallContractWithOverrides(ctx context.Context, msg ethereum.CallMsg, overrides ethereum.StateOverride) ([]byte, error) {
var hex hexutil.Bytes
err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), "pending")
err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), "pending", overrides)
if err != nil {
return nil, err
}
Expand Down
43 changes: 42 additions & 1 deletion ethclient/ethclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
Expand Down Expand Up @@ -263,6 +264,9 @@ func TestEthClient(t *testing.T) {
"TestCallContract": {
func(t *testing.T) { testCallContract(t, client) },
},
"TestCallContractWithOverrides": {
func(t *testing.T) { testCallContractWithOverrides(t, client) },
},
"TestAtFunctions": {
func(t *testing.T) { testAtFunctions(t, client) },
},
Expand Down Expand Up @@ -492,12 +496,49 @@ func testCallContract(t *testing.T, client *rpc.Client) {
if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// PendingCallCOntract
// PendingCallContract
if _, err := ec.PendingCallContract(context.Background(), msg); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

func testCallContractWithOverrides(t *testing.T, client *rpc.Client) {
ec := NewClient(client)

nonExistentAccount := common.Address{1}

msg := ethereum.CallMsg{
From: nonExistentAccount,
To: &common.Address{},
Gas: 21000,
Value: big.NewInt(1),
}

// CallContract should fail due to lack of funds
if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err == nil {
t.Fatalf("expected error sending from empty account")
}
// PendingCallContract should fail due to lack of funds
if _, err := ec.PendingCallContract(context.Background(), msg); err == nil {
t.Fatalf("expected error sending from empty account")
}

// Override
overrides := make(ethereum.StateOverride)
balance := new(*hexutil.Big)
*balance = (*hexutil.Big)(big.NewInt(1))
overrides[nonExistentAccount] = ethereum.OverrideAccount{Balance: balance}

// CallContractWithOverrides should succeed with overridden balance
if _, err := ec.CallContractWithOverrides(context.Background(), msg, big.NewInt(1), overrides); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// PendingCallContractWithOverrides should succeed with overridden balance
if _, err := ec.PendingCallContractWithOverrides(context.Background(), msg, overrides); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

func testAtFunctions(t *testing.T, client *rpc.Client) {
ec := NewClient(client)
// send a transaction for some interesting pending status
Expand Down
18 changes: 18 additions & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)

Expand Down Expand Up @@ -133,6 +134,23 @@ type ContractCaller interface {
CallContract(ctx context.Context, call CallMsg, blockNumber *big.Int) ([]byte, error)
}

// 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
// if statDiff is set, all diff will be applied first and then execute the call
// message.
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"`
}

// StateOverride is the collection of overridden accounts.
type StateOverride map[common.Address]OverrideAccount

// FilterQuery contains options for contract log filtering.
type FilterQuery struct {
BlockHash *common.Hash // used by eth_getLogs, return logs only from block with this hash
Expand Down
17 changes: 2 additions & 15 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"time"

"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/keystore"
Expand Down Expand Up @@ -804,22 +805,8 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A
return res[:], state.Error()
}

// 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
// if statDiff is set, all diff will be applied first and then execute the call
// message.
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"`
}

// StateOverride is the collection of overridden accounts.
type StateOverride map[common.Address]OverrideAccount
type StateOverride ethereum.StateOverride

// Apply overrides the fields of specified accounts into the given state.
func (diff *StateOverride) Apply(state *state.StateDB) error {
Expand Down

0 comments on commit 0be7ed6

Please sign in to comment.