abi/base: return error for pending call error (#24649)
If a pending contract call errors, return that error right away rather than ignoring it to allow an error somewhere else. This is helpful for callers to know if perhaps a call failed because of the context deadline being expired. This change mirrors the behavior of non-pending contract calls.
This commit is contained in:
parent
195c979168
commit
eb69f490ed
@ -171,7 +171,10 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
|
|||||||
return ErrNoPendingState
|
return ErrNoPendingState
|
||||||
}
|
}
|
||||||
output, err = pb.PendingCallContract(ctx, msg)
|
output, err = pb.PendingCallContract(ctx, msg)
|
||||||
if err == nil && len(output) == 0 {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(output) == 0 {
|
||||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||||
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
|
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -18,6 +18,7 @@ package bind_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@ -75,34 +76,51 @@ func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transac
|
|||||||
}
|
}
|
||||||
|
|
||||||
type mockCaller struct {
|
type mockCaller struct {
|
||||||
codeAtBlockNumber *big.Int
|
codeAtBlockNumber *big.Int
|
||||||
callContractBlockNumber *big.Int
|
callContractBlockNumber *big.Int
|
||||||
pendingCodeAtCalled bool
|
callContractBytes []byte
|
||||||
pendingCallContractCalled bool
|
callContractErr error
|
||||||
|
codeAtBytes []byte
|
||||||
|
codeAtErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
||||||
mc.codeAtBlockNumber = blockNumber
|
mc.codeAtBlockNumber = blockNumber
|
||||||
return []byte{1, 2, 3}, nil
|
return mc.codeAtBytes, mc.codeAtErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||||
mc.callContractBlockNumber = blockNumber
|
mc.callContractBlockNumber = blockNumber
|
||||||
return nil, nil
|
return mc.callContractBytes, mc.callContractErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mockCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
type mockPendingCaller struct {
|
||||||
|
*mockCaller
|
||||||
|
pendingCodeAtBytes []byte
|
||||||
|
pendingCodeAtErr error
|
||||||
|
pendingCodeAtCalled bool
|
||||||
|
pendingCallContractCalled bool
|
||||||
|
pendingCallContractBytes []byte
|
||||||
|
pendingCallContractErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockPendingCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
||||||
mc.pendingCodeAtCalled = true
|
mc.pendingCodeAtCalled = true
|
||||||
return nil, nil
|
return mc.pendingCodeAtBytes, mc.pendingCodeAtErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mockCaller) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
||||||
mc.pendingCallContractCalled = true
|
mc.pendingCallContractCalled = true
|
||||||
return nil, nil
|
return mc.pendingCallContractBytes, mc.pendingCallContractErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPassingBlockNumber(t *testing.T) {
|
func TestPassingBlockNumber(t *testing.T) {
|
||||||
|
|
||||||
mc := &mockCaller{}
|
mc := &mockPendingCaller{
|
||||||
|
mockCaller: &mockCaller{
|
||||||
|
codeAtBytes: []byte{1, 2, 3},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
|
||||||
Methods: map[string]abi.Method{
|
Methods: map[string]abi.Method{
|
||||||
@ -341,3 +359,132 @@ func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
|
|||||||
Removed: false,
|
Removed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCall(t *testing.T) {
|
||||||
|
var method, methodWithArg = "something", "somethingArrrrg"
|
||||||
|
tests := []struct {
|
||||||
|
name, method string
|
||||||
|
opts *bind.CallOpts
|
||||||
|
mc bind.ContractCaller
|
||||||
|
results *[]interface{}
|
||||||
|
wantErr bool
|
||||||
|
wantErrExact error
|
||||||
|
}{{
|
||||||
|
name: "ok not pending",
|
||||||
|
mc: &mockCaller{
|
||||||
|
codeAtBytes: []byte{0},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
}, {
|
||||||
|
name: "ok pending",
|
||||||
|
mc: &mockPendingCaller{
|
||||||
|
pendingCodeAtBytes: []byte{0},
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
}, {
|
||||||
|
name: "pack error, no method",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
method: "else",
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "interface error, pending but not a PendingContractCaller",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoPendingState,
|
||||||
|
}, {
|
||||||
|
name: "pending call canceled",
|
||||||
|
mc: &mockPendingCaller{
|
||||||
|
pendingCallContractErr: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: context.DeadlineExceeded,
|
||||||
|
}, {
|
||||||
|
name: "pending code at error",
|
||||||
|
mc: &mockPendingCaller{
|
||||||
|
pendingCodeAtErr: errors.New(""),
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "no pending code at",
|
||||||
|
mc: new(mockPendingCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
Pending: true,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoCode,
|
||||||
|
}, {
|
||||||
|
name: "call contract error",
|
||||||
|
mc: &mockCaller{
|
||||||
|
callContractErr: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: context.DeadlineExceeded,
|
||||||
|
}, {
|
||||||
|
name: "code at error",
|
||||||
|
mc: &mockCaller{
|
||||||
|
codeAtErr: errors.New(""),
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "no code at",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoCode,
|
||||||
|
}, {
|
||||||
|
name: "unpack error missing arg",
|
||||||
|
mc: &mockCaller{
|
||||||
|
codeAtBytes: []byte{0},
|
||||||
|
},
|
||||||
|
method: methodWithArg,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "interface unpack error",
|
||||||
|
mc: &mockCaller{
|
||||||
|
codeAtBytes: []byte{0},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
results: &[]interface{}{0},
|
||||||
|
wantErr: true,
|
||||||
|
}}
|
||||||
|
for _, test := range tests {
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
|
||||||
|
Methods: map[string]abi.Method{
|
||||||
|
method: {
|
||||||
|
Name: method,
|
||||||
|
Outputs: abi.Arguments{},
|
||||||
|
},
|
||||||
|
methodWithArg: {
|
||||||
|
Name: methodWithArg,
|
||||||
|
Outputs: abi.Arguments{abi.Argument{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, test.mc, nil, nil)
|
||||||
|
err := bc.Call(test.opts, test.results, test.method)
|
||||||
|
if test.wantErr || test.wantErrExact != nil {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%q expected error", test.name)
|
||||||
|
}
|
||||||
|
if test.wantErrExact != nil && !errors.Is(err, test.wantErrExact) {
|
||||||
|
t.Fatalf("%q expected error %q but got %q", test.name, test.wantErrExact, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%q unexpected error: %v", test.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user