Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ethclient: make CallContract also accept block hash #24354

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion accounts/abi/bind/backend.go
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
)

var (
Expand All @@ -50,7 +51,7 @@ type ContractCaller interface {

// CallContract executes an Ethereum contract call with the specified data as the
// input.
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumberOrHash rpc.BlockNumberOrHash) ([]byte, error)
}

// PendingContractCaller defines methods to perform contract calls on the pending state.
Expand Down
5 changes: 3 additions & 2 deletions accounts/abi/bind/backends/simulated.go
Expand Up @@ -414,11 +414,12 @@ func (e *revertError) ErrorData() interface{} {
}

// CallContract executes a contract call.
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumberOrHash rpc.BlockNumberOrHash) ([]byte, error) {
b.mu.Lock()
defer b.mu.Unlock()

if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
currentBlock := b.blockchain.CurrentBlock()
if (blockNumberOrHash.BlockHash != nil && *blockNumberOrHash.BlockHash != currentBlock.Hash()) || (blockNumberOrHash.BlockNumber != nil && *blockNumberOrHash.BlockNumber != rpc.BlockNumber(currentBlock.Number().Int64())) {
return nil, errBlockNumberUnsupported
}
stateDB, err := b.blockchain.State()
Expand Down
5 changes: 3 additions & 2 deletions accounts/abi/bind/backends/simulated_test.go
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)

func TestSimulatedBackend(t *testing.T) {
Expand Down Expand Up @@ -1042,7 +1043,7 @@ func TestPendingAndCallContract(t *testing.T) {
From: testAddr,
To: &addr,
Data: input,
}, nil)
}, rpc.BlockNumberOrHash{})
if err != nil {
t.Errorf("could not call receive method on contract: %v", err)
}
Expand Down Expand Up @@ -1117,7 +1118,7 @@ func TestCallContractRevert(t *testing.T) {
From: testAddr,
To: &addr,
Data: input,
}, nil)
}, rpc.BlockNumberOrHash{})
}

// Run pending calls then commit
Expand Down
15 changes: 14 additions & 1 deletion accounts/abi/bind/base.go
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
)

// SignerFn is a signer function callback when a contract requires a method to
Expand All @@ -41,6 +42,7 @@ type CallOpts struct {
Pending bool // Whether to operate on the pending state or the last known one
From common.Address // Optional the sender address, otherwise the first account is used
BlockNumber *big.Int // Optional the block number on which the call should be performed
BlockHash *common.Hash // Optional the block hash on which the call should be performed
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
}

Expand Down Expand Up @@ -180,7 +182,18 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
}
}
} else {
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
blockNumberOrHash := rpc.BlockNumberOrHash{}
if opts.BlockNumber != nil {
blockNumber := rpc.BlockNumber(opts.BlockNumber.Int64())
blockNumberOrHash.BlockNumber = &blockNumber
}
if opts.BlockHash != nil {
if blockNumberOrHash.BlockNumber != nil {
return fmt.Errorf("cannot specify both BlockHash and BlockNumber, choose one or the other")
}
blockNumberOrHash.BlockHash = opts.BlockHash
}
output, err = c.caller.CallContract(ctx, msg, blockNumberOrHash)
if err != nil {
return err
}
Expand Down
17 changes: 9 additions & 8 deletions accounts/abi/bind/base_test.go
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -75,19 +76,19 @@ func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transac
}

type mockCaller struct {
codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int
pendingCodeAtCalled bool
pendingCallContractCalled bool
codeAtBlockNumber *big.Int
callContractBlockNumberOrHash rpc.BlockNumberOrHash
pendingCodeAtCalled bool
pendingCallContractCalled bool
}

func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
mc.codeAtBlockNumber = blockNumber
return []byte{1, 2, 3}, nil
}

func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
mc.callContractBlockNumber = blockNumber
func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumberOrHash rpc.BlockNumberOrHash) ([]byte, error) {
mc.callContractBlockNumberOrHash = blockNumberOrHash
return nil, nil
}

Expand Down Expand Up @@ -117,7 +118,7 @@ func TestPassingBlockNumber(t *testing.T) {

bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, nil, "something")

if mc.callContractBlockNumber != blockNumber {
if *mc.callContractBlockNumberOrHash.BlockNumber != rpc.BlockNumber(blockNumber.Int64()) {
t.Fatalf("CallContract() was not passed the block number")
}

Expand All @@ -127,7 +128,7 @@ func TestPassingBlockNumber(t *testing.T) {

bc.Call(&bind.CallOpts{}, nil, "something")

if mc.callContractBlockNumber != nil {
if mc.callContractBlockNumberOrHash.BlockNumber != nil {
t.Fatalf("CallContract() was passed a block number when it should not have been")
}

Expand Down
10 changes: 7 additions & 3 deletions ethclient/ethclient.go
Expand Up @@ -444,12 +444,16 @@ func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) {
// CallContract 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
// blockNumberOrHash selects the block height or block hash at which the call runs. It can be empty, in which
// 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) {
func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumberOrHash rpc.BlockNumberOrHash) ([]byte, error) {
if blockNumberOrHash.BlockHash == nil && blockNumberOrHash.BlockNumber == nil {
latest := rpc.BlockNumber(rpc.LatestBlockNumber)
blockNumberOrHash.BlockNumber = &latest
}
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), blockNumberOrHash)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion ethclient/ethclient_test.go
Expand Up @@ -525,7 +525,7 @@ func testCallContract(t *testing.T, client *rpc.Client) {
t.Fatalf("unexpected gas price: %v", gas)
}
// CallContract
if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil {
if _, err := ec.CallContract(context.Background(), msg, rpc.BlockNumberOrHashWithNumber(1)); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// PendingCallCOntract
Expand Down
3 changes: 2 additions & 1 deletion interfaces.go
Expand Up @@ -24,6 +24,7 @@ import (

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

// NotFound is returned by API methods if the requested item does not exist.
Expand Down Expand Up @@ -149,7 +150,7 @@ type CallMsg struct {
// execute such calls. For applications which are structured around specific contracts,
// the abigen tool provides a nicer, properly typed way to perform calls.
type ContractCaller interface {
CallContract(ctx context.Context, call CallMsg, blockNumber *big.Int) ([]byte, error)
CallContract(ctx context.Context, call CallMsg, blockNumberOrHash rpc.BlockNumberOrHash) ([]byte, error)
}

// FilterQuery contains options for contract log filtering.
Expand Down
5 changes: 3 additions & 2 deletions mobile/ethclient.go
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
)

// EthereumClient provides access to the Ethereum APIs.
Expand Down Expand Up @@ -280,9 +281,9 @@ func (ec *EthereumClient) GetPendingTransactionCount(ctx *Context) (count int, _
// blocks might not be available.
func (ec *EthereumClient) CallContract(ctx *Context, msg *CallMsg, number int64) (output []byte, _ error) {
if number < 0 {
return ec.client.CallContract(ctx.context, msg.msg, nil)
return ec.client.CallContract(ctx.context, msg.msg, rpc.BlockNumberOrHash{})
}
return ec.client.CallContract(ctx.context, msg.msg, big.NewInt(number))
return ec.client.CallContract(ctx.context, msg.msg, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(number)))
}

// PendingCallContract executes a message call transaction using the EVM.
Expand Down