api/bind: add CallOpts.BlockHash to allow calling contracts at a specific block hash (#28084)
* api/bind: Add CallOpts.BlockHash to allow calling contracts at a specific block hash. * ethclient: Add BalanceAtHash, NonceAtHash and StorageAtHash functions
This commit is contained in:
parent
f62502e123
commit
b85c86022e
|
@ -36,6 +36,10 @@ var (
|
||||||
// on a backend that doesn't implement PendingContractCaller.
|
// on a backend that doesn't implement PendingContractCaller.
|
||||||
ErrNoPendingState = errors.New("backend does not support pending state")
|
ErrNoPendingState = errors.New("backend does not support pending state")
|
||||||
|
|
||||||
|
// ErrNoBlockHashState is raised when attempting to perform a block hash action
|
||||||
|
// on a backend that doesn't implement BlockHashContractCaller.
|
||||||
|
ErrNoBlockHashState = errors.New("backend does not support block hash state")
|
||||||
|
|
||||||
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
||||||
// an empty contract behind.
|
// an empty contract behind.
|
||||||
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
|
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
|
||||||
|
@ -64,6 +68,17 @@ type PendingContractCaller interface {
|
||||||
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
|
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockHashContractCaller defines methods to perform contract calls on a specific block hash.
|
||||||
|
// Call will try to discover this interface when access to a block by hash is requested.
|
||||||
|
// If the backend does not support the block hash state, Call returns ErrNoBlockHashState.
|
||||||
|
type BlockHashContractCaller interface {
|
||||||
|
// CodeAtHash returns the code of the given account in the state at the specified block hash.
|
||||||
|
CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error)
|
||||||
|
|
||||||
|
// CallContractAtHash executes an Ethereum contract all against the state at the specified block hash.
|
||||||
|
CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
// ContractTransactor defines the methods needed to allow operating with a contract
|
// ContractTransactor defines the methods needed to allow operating with a contract
|
||||||
// on a write only basis. Besides the transacting method, the remainder are helpers
|
// on a write only basis. Besides the transacting method, the remainder are helpers
|
||||||
// used when the user does not provide some needed values, but rather leaves it up
|
// used when the user does not provide some needed values, but rather leaves it up
|
||||||
|
|
|
@ -50,6 +50,7 @@ var _ bind.ContractBackend = (*SimulatedBackend)(nil)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
|
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
|
||||||
|
errBlockHashUnsupported = errors.New("simulatedBackend cannot access blocks by hash other than the latest block")
|
||||||
errBlockDoesNotExist = errors.New("block does not exist in blockchain")
|
errBlockDoesNotExist = errors.New("block does not exist in blockchain")
|
||||||
errTransactionDoesNotExist = errors.New("transaction does not exist")
|
errTransactionDoesNotExist = errors.New("transaction does not exist")
|
||||||
)
|
)
|
||||||
|
@ -202,6 +203,24 @@ func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address,
|
||||||
return stateDB.GetCode(contract), nil
|
return stateDB.GetCode(contract), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CodeAtHash returns the code associated with a certain account in the blockchain.
|
||||||
|
func (b *SimulatedBackend) CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
header, err := b.headerByHash(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stateDB, err := b.blockchain.StateAt(header.Root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateDB.GetCode(contract), nil
|
||||||
|
}
|
||||||
|
|
||||||
// BalanceAt returns the wei balance of a certain account in the blockchain.
|
// BalanceAt returns the wei balance of a certain account in the blockchain.
|
||||||
func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) {
|
func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
|
@ -320,7 +339,11 @@ func (b *SimulatedBackend) blockByNumber(ctx context.Context, number *big.Int) (
|
||||||
func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
return b.headerByHash(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerByHash retrieves a header from the database by hash without Lock.
|
||||||
|
func (b *SimulatedBackend) headerByHash(hash common.Hash) (*types.Header, error) {
|
||||||
if hash == b.pendingBlock.Hash() {
|
if hash == b.pendingBlock.Hash() {
|
||||||
return b.pendingBlock.Header(), nil
|
return b.pendingBlock.Header(), nil
|
||||||
}
|
}
|
||||||
|
@ -436,6 +459,22 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM
|
||||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 {
|
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 {
|
||||||
return nil, errBlockNumberUnsupported
|
return nil, errBlockNumberUnsupported
|
||||||
}
|
}
|
||||||
|
return b.callContractAtHead(ctx, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallContractAtHash executes a contract call on a specific block hash.
|
||||||
|
func (b *SimulatedBackend) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
if blockHash != b.blockchain.CurrentBlock().Hash() {
|
||||||
|
return nil, errBlockHashUnsupported
|
||||||
|
}
|
||||||
|
return b.callContractAtHead(ctx, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
// callContractAtHead executes a contract call against the latest block state.
|
||||||
|
func (b *SimulatedBackend) callContractAtHead(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
||||||
stateDB, err := b.blockchain.State()
|
stateDB, err := b.blockchain.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -996,6 +996,43 @@ func TestCodeAt(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCodeAtHash(t *testing.T) {
|
||||||
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
sim := simTestBackend(testAddr)
|
||||||
|
defer sim.Close()
|
||||||
|
bgCtx := context.Background()
|
||||||
|
code, err := sim.CodeAtHash(bgCtx, testAddr, sim.Blockchain().CurrentHeader().Hash())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not get code at test addr: %v", err)
|
||||||
|
}
|
||||||
|
if len(code) != 0 {
|
||||||
|
t.Errorf("got code for account that does not have contract code")
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := abi.JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not get code at test addr: %v", err)
|
||||||
|
}
|
||||||
|
auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337))
|
||||||
|
contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockHash := sim.Commit()
|
||||||
|
code, err = sim.CodeAtHash(bgCtx, contractAddr, blockHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not get code at test addr: %v", err)
|
||||||
|
}
|
||||||
|
if len(code) == 0 {
|
||||||
|
t.Errorf("did not get code for account that has contract code")
|
||||||
|
}
|
||||||
|
// ensure code received equals code deployed
|
||||||
|
if !bytes.Equal(code, common.FromHex(deployedCode)) {
|
||||||
|
t.Errorf("code received did not match expected deployed code:\n expected %v\n actual %v", common.FromHex(deployedCode), code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
|
// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
|
||||||
//
|
//
|
||||||
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
||||||
|
@ -1038,7 +1075,7 @@ func TestPendingAndCallContract(t *testing.T) {
|
||||||
t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res))
|
t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
sim.Commit()
|
blockHash := sim.Commit()
|
||||||
|
|
||||||
// make sure you can call the contract
|
// make sure you can call the contract
|
||||||
res, err = sim.CallContract(bgCtx, ethereum.CallMsg{
|
res, err = sim.CallContract(bgCtx, ethereum.CallMsg{
|
||||||
|
@ -1056,6 +1093,23 @@ func TestPendingAndCallContract(t *testing.T) {
|
||||||
if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") {
|
if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") {
|
||||||
t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res))
|
t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure you can call the contract by hash
|
||||||
|
res, err = sim.CallContractAtHash(bgCtx, ethereum.CallMsg{
|
||||||
|
From: testAddr,
|
||||||
|
To: &addr,
|
||||||
|
Data: input,
|
||||||
|
}, blockHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not call receive method on contract: %v", err)
|
||||||
|
}
|
||||||
|
if len(res) == 0 {
|
||||||
|
t.Errorf("result of contract call was empty: %v", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") {
|
||||||
|
t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test is based on the following contract:
|
// This test is based on the following contract:
|
||||||
|
|
|
@ -48,6 +48,7 @@ type CallOpts struct {
|
||||||
Pending bool // Whether to operate on the pending state or the last known one
|
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
|
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
|
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)
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,6 +190,23 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
|
||||||
return ErrNoCode
|
return ErrNoCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if opts.BlockHash != (common.Hash{}) {
|
||||||
|
bh, ok := c.caller.(BlockHashContractCaller)
|
||||||
|
if !ok {
|
||||||
|
return ErrNoBlockHashState
|
||||||
|
}
|
||||||
|
output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(output) == 0 {
|
||||||
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||||
|
if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(code) == 0 {
|
||||||
|
return ErrNoCode
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -114,6 +114,26 @@ func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ether
|
||||||
return mc.pendingCallContractBytes, mc.pendingCallContractErr
|
return mc.pendingCallContractBytes, mc.pendingCallContractErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockBlockHashCaller struct {
|
||||||
|
*mockCaller
|
||||||
|
codeAtHashBytes []byte
|
||||||
|
codeAtHashErr error
|
||||||
|
codeAtHashCalled bool
|
||||||
|
callContractAtHashCalled bool
|
||||||
|
callContractAtHashBytes []byte
|
||||||
|
callContractAtHashErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockBlockHashCaller) CodeAtHash(ctx context.Context, contract common.Address, hash common.Hash) ([]byte, error) {
|
||||||
|
mc.codeAtHashCalled = true
|
||||||
|
return mc.codeAtHashBytes, mc.codeAtHashErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockBlockHashCaller) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, hash common.Hash) ([]byte, error) {
|
||||||
|
mc.callContractAtHashCalled = true
|
||||||
|
return mc.callContractAtHashBytes, mc.callContractAtHashErr
|
||||||
|
}
|
||||||
|
|
||||||
func TestPassingBlockNumber(t *testing.T) {
|
func TestPassingBlockNumber(t *testing.T) {
|
||||||
mc := &mockPendingCaller{
|
mc := &mockPendingCaller{
|
||||||
mockCaller: &mockCaller{
|
mockCaller: &mockCaller{
|
||||||
|
@ -400,6 +420,15 @@ func TestCall(t *testing.T) {
|
||||||
Pending: true,
|
Pending: true,
|
||||||
},
|
},
|
||||||
method: method,
|
method: method,
|
||||||
|
}, {
|
||||||
|
name: "ok hash",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
codeAtHashBytes: []byte{0},
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
}, {
|
}, {
|
||||||
name: "pack error, no method",
|
name: "pack error, no method",
|
||||||
mc: new(mockCaller),
|
mc: new(mockCaller),
|
||||||
|
@ -413,6 +442,14 @@ func TestCall(t *testing.T) {
|
||||||
},
|
},
|
||||||
method: method,
|
method: method,
|
||||||
wantErrExact: bind.ErrNoPendingState,
|
wantErrExact: bind.ErrNoPendingState,
|
||||||
|
}, {
|
||||||
|
name: "interface error, blockHash but not a BlockHashContractCaller",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoBlockHashState,
|
||||||
}, {
|
}, {
|
||||||
name: "pending call canceled",
|
name: "pending call canceled",
|
||||||
mc: &mockPendingCaller{
|
mc: &mockPendingCaller{
|
||||||
|
@ -460,6 +497,34 @@ func TestCall(t *testing.T) {
|
||||||
mc: new(mockCaller),
|
mc: new(mockCaller),
|
||||||
method: method,
|
method: method,
|
||||||
wantErrExact: bind.ErrNoCode,
|
wantErrExact: bind.ErrNoCode,
|
||||||
|
}, {
|
||||||
|
name: "call contract at hash error",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
callContractAtHashErr: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: context.DeadlineExceeded,
|
||||||
|
}, {
|
||||||
|
name: "code at error",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
codeAtHashErr: errors.New(""),
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "no code at hash",
|
||||||
|
mc: new(mockBlockHashCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoCode,
|
||||||
}, {
|
}, {
|
||||||
name: "unpack error missing arg",
|
name: "unpack error missing arg",
|
||||||
mc: &mockCaller{
|
mc: &mockCaller{
|
||||||
|
|
|
@ -370,6 +370,13 @@ func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNu
|
||||||
return (*big.Int)(&result), err
|
return (*big.Int)(&result), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BalanceAtHash returns the wei balance of the given account.
|
||||||
|
func (ec *Client) BalanceAtHash(ctx context.Context, account common.Address, blockHash common.Hash) (*big.Int, error) {
|
||||||
|
var result hexutil.Big
|
||||||
|
err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, rpc.BlockNumberOrHashWithHash(blockHash, false))
|
||||||
|
return (*big.Int)(&result), err
|
||||||
|
}
|
||||||
|
|
||||||
// StorageAt returns the value of key in the contract storage of the given account.
|
// StorageAt returns the value of key in the contract storage of the given account.
|
||||||
// The block number can be nil, in which case the value is taken from the latest known block.
|
// The block number can be nil, in which case the value is taken from the latest known block.
|
||||||
func (ec *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
|
func (ec *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
|
||||||
|
@ -378,6 +385,13 @@ func (ec *Client) StorageAt(ctx context.Context, account common.Address, key com
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StorageAtHash returns the value of key in the contract storage of the given account.
|
||||||
|
func (ec *Client) StorageAtHash(ctx context.Context, account common.Address, key common.Hash, blockHash common.Hash) ([]byte, error) {
|
||||||
|
var result hexutil.Bytes
|
||||||
|
err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, rpc.BlockNumberOrHashWithHash(blockHash, false))
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
// CodeAt returns the contract code of the given account.
|
// CodeAt returns the contract code of the given account.
|
||||||
// The block number can be nil, in which case the code is taken from the latest known block.
|
// The block number can be nil, in which case the code is taken from the latest known block.
|
||||||
func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
|
func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
|
||||||
|
@ -386,6 +400,13 @@ func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumbe
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CodeAtHash returns the contract code of the given account.
|
||||||
|
func (ec *Client) CodeAtHash(ctx context.Context, account common.Address, blockHash common.Hash) ([]byte, error) {
|
||||||
|
var result hexutil.Bytes
|
||||||
|
err := ec.c.CallContext(ctx, &result, "eth_getCode", account, rpc.BlockNumberOrHashWithHash(blockHash, false))
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
// NonceAt returns the account nonce of the given account.
|
// NonceAt returns the account nonce of the given account.
|
||||||
// The block number can be nil, in which case the nonce is taken from the latest known block.
|
// The block number can be nil, in which case the nonce is taken from the latest known block.
|
||||||
func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
|
func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
|
||||||
|
@ -394,6 +415,13 @@ func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumb
|
||||||
return uint64(result), err
|
return uint64(result), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NonceAtHash returns the account nonce of the given account.
|
||||||
|
func (ec *Client) NonceAtHash(ctx context.Context, account common.Address, blockHash common.Hash) (uint64, error) {
|
||||||
|
var result hexutil.Uint64
|
||||||
|
err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, rpc.BlockNumberOrHashWithHash(blockHash, false))
|
||||||
|
return uint64(result), err
|
||||||
|
}
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
|
|
||||||
// FilterLogs executes a filter query.
|
// FilterLogs executes a filter query.
|
||||||
|
|
|
@ -584,6 +584,11 @@ func testCallContract(t *testing.T, client *rpc.Client) {
|
||||||
func testAtFunctions(t *testing.T, client *rpc.Client) {
|
func testAtFunctions(t *testing.T, client *rpc.Client) {
|
||||||
ec := NewClient(client)
|
ec := NewClient(client)
|
||||||
|
|
||||||
|
block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BlockByNumber error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// send a transaction for some interesting pending status
|
// send a transaction for some interesting pending status
|
||||||
sendTransaction(ec)
|
sendTransaction(ec)
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
@ -601,6 +606,13 @@ func testAtFunctions(t *testing.T, client *rpc.Client) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
hashBalance, err := ec.BalanceAtHash(context.Background(), testAddr, block.Hash())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if balance.Cmp(hashBalance) == 0 {
|
||||||
|
t.Fatalf("unexpected balance at hash: %v %v", balance, hashBalance)
|
||||||
|
}
|
||||||
penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr)
|
penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
@ -613,6 +625,13 @@ func testAtFunctions(t *testing.T, client *rpc.Client) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
hashNonce, err := ec.NonceAtHash(context.Background(), testAddr, block.Hash())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if hashNonce == nonce {
|
||||||
|
t.Fatalf("unexpected nonce at hash: %v %v", nonce, hashNonce)
|
||||||
|
}
|
||||||
penNonce, err := ec.PendingNonceAt(context.Background(), testAddr)
|
penNonce, err := ec.PendingNonceAt(context.Background(), testAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
@ -625,6 +644,13 @@ func testAtFunctions(t *testing.T, client *rpc.Client) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
hashStorage, err := ec.StorageAtHash(context.Background(), testAddr, common.Hash{}, block.Hash())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(storage, hashStorage) {
|
||||||
|
t.Fatalf("unexpected storage at hash: %v %v", storage, hashStorage)
|
||||||
|
}
|
||||||
penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{})
|
penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
@ -637,6 +663,13 @@ func testAtFunctions(t *testing.T, client *rpc.Client) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
hashCode, err := ec.CodeAtHash(context.Background(), common.Address{}, block.Hash())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(code, hashCode) {
|
||||||
|
t.Fatalf("unexpected code at hash: %v %v", code, hashCode)
|
||||||
|
}
|
||||||
penCode, err := ec.PendingCodeAt(context.Background(), testAddr)
|
penCode, err := ec.PendingCodeAt(context.Background(), testAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
|
Loading…
Reference in New Issue