github.com/ethereum/go-ethereum@v1.14.3/accounts/abi/bind/base_test.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package bind_test
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"math/big"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/ethereum/go-ethereum"
    28  	"github.com/ethereum/go-ethereum/accounts/abi"
    29  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    30  	"github.com/ethereum/go-ethereum/common"
    31  	"github.com/ethereum/go-ethereum/common/hexutil"
    32  	"github.com/ethereum/go-ethereum/core/types"
    33  	"github.com/ethereum/go-ethereum/crypto"
    34  	"github.com/ethereum/go-ethereum/rlp"
    35  	"github.com/stretchr/testify/assert"
    36  )
    37  
    38  func mockSign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { return tx, nil }
    39  
    40  type mockTransactor struct {
    41  	baseFee                *big.Int
    42  	gasTipCap              *big.Int
    43  	gasPrice               *big.Int
    44  	suggestGasTipCapCalled bool
    45  	suggestGasPriceCalled  bool
    46  }
    47  
    48  func (mt *mockTransactor) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
    49  	return &types.Header{BaseFee: mt.baseFee}, nil
    50  }
    51  
    52  func (mt *mockTransactor) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
    53  	return []byte{1}, nil
    54  }
    55  
    56  func (mt *mockTransactor) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
    57  	return 0, nil
    58  }
    59  
    60  func (mt *mockTransactor) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
    61  	mt.suggestGasPriceCalled = true
    62  	return mt.gasPrice, nil
    63  }
    64  
    65  func (mt *mockTransactor) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
    66  	mt.suggestGasTipCapCalled = true
    67  	return mt.gasTipCap, nil
    68  }
    69  
    70  func (mt *mockTransactor) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
    71  	return 0, nil
    72  }
    73  
    74  func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transaction) error {
    75  	return nil
    76  }
    77  
    78  type mockCaller struct {
    79  	codeAtBlockNumber       *big.Int
    80  	callContractBlockNumber *big.Int
    81  	callContractBytes       []byte
    82  	callContractErr         error
    83  	codeAtBytes             []byte
    84  	codeAtErr               error
    85  }
    86  
    87  func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
    88  	mc.codeAtBlockNumber = blockNumber
    89  	return mc.codeAtBytes, mc.codeAtErr
    90  }
    91  
    92  func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
    93  	mc.callContractBlockNumber = blockNumber
    94  	return mc.callContractBytes, mc.callContractErr
    95  }
    96  
    97  type mockPendingCaller struct {
    98  	*mockCaller
    99  	pendingCodeAtBytes        []byte
   100  	pendingCodeAtErr          error
   101  	pendingCodeAtCalled       bool
   102  	pendingCallContractCalled bool
   103  	pendingCallContractBytes  []byte
   104  	pendingCallContractErr    error
   105  }
   106  
   107  func (mc *mockPendingCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
   108  	mc.pendingCodeAtCalled = true
   109  	return mc.pendingCodeAtBytes, mc.pendingCodeAtErr
   110  }
   111  
   112  func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
   113  	mc.pendingCallContractCalled = true
   114  	return mc.pendingCallContractBytes, mc.pendingCallContractErr
   115  }
   116  
   117  type mockBlockHashCaller struct {
   118  	*mockCaller
   119  	codeAtHashBytes          []byte
   120  	codeAtHashErr            error
   121  	codeAtHashCalled         bool
   122  	callContractAtHashCalled bool
   123  	callContractAtHashBytes  []byte
   124  	callContractAtHashErr    error
   125  }
   126  
   127  func (mc *mockBlockHashCaller) CodeAtHash(ctx context.Context, contract common.Address, hash common.Hash) ([]byte, error) {
   128  	mc.codeAtHashCalled = true
   129  	return mc.codeAtHashBytes, mc.codeAtHashErr
   130  }
   131  
   132  func (mc *mockBlockHashCaller) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, hash common.Hash) ([]byte, error) {
   133  	mc.callContractAtHashCalled = true
   134  	return mc.callContractAtHashBytes, mc.callContractAtHashErr
   135  }
   136  
   137  func TestPassingBlockNumber(t *testing.T) {
   138  	t.Parallel()
   139  	mc := &mockPendingCaller{
   140  		mockCaller: &mockCaller{
   141  			codeAtBytes: []byte{1, 2, 3},
   142  		},
   143  	}
   144  
   145  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
   146  		Methods: map[string]abi.Method{
   147  			"something": {
   148  				Name:    "something",
   149  				Outputs: abi.Arguments{},
   150  			},
   151  		},
   152  	}, mc, nil, nil)
   153  
   154  	blockNumber := big.NewInt(42)
   155  
   156  	bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, nil, "something")
   157  
   158  	if mc.callContractBlockNumber != blockNumber {
   159  		t.Fatalf("CallContract() was not passed the block number")
   160  	}
   161  
   162  	if mc.codeAtBlockNumber != blockNumber {
   163  		t.Fatalf("CodeAt() was not passed the block number")
   164  	}
   165  
   166  	bc.Call(&bind.CallOpts{}, nil, "something")
   167  
   168  	if mc.callContractBlockNumber != nil {
   169  		t.Fatalf("CallContract() was passed a block number when it should not have been")
   170  	}
   171  
   172  	if mc.codeAtBlockNumber != nil {
   173  		t.Fatalf("CodeAt() was passed a block number when it should not have been")
   174  	}
   175  
   176  	bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, nil, "something")
   177  
   178  	if !mc.pendingCallContractCalled {
   179  		t.Fatalf("CallContract() was not passed the block number")
   180  	}
   181  
   182  	if !mc.pendingCodeAtCalled {
   183  		t.Fatalf("CodeAt() was not passed the block number")
   184  	}
   185  }
   186  
   187  const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
   188  
   189  func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
   190  	t.Parallel()
   191  	hash := crypto.Keccak256Hash([]byte("testName"))
   192  	topics := []common.Hash{
   193  		crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
   194  		hash,
   195  	}
   196  	mockLog := newMockLog(topics, common.HexToHash("0x0"))
   197  
   198  	abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
   199  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   200  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   201  
   202  	expectedReceivedMap := map[string]interface{}{
   203  		"name":   hash,
   204  		"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   205  		"amount": big.NewInt(1),
   206  		"memo":   []byte{88},
   207  	}
   208  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   209  }
   210  
   211  func TestUnpackAnonymousLogIntoMap(t *testing.T) {
   212  	t.Parallel()
   213  	mockLog := newMockLog(nil, common.HexToHash("0x0"))
   214  
   215  	abiString := `[{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"received","type":"event"}]`
   216  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   217  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   218  
   219  	var received map[string]interface{}
   220  	err := bc.UnpackLogIntoMap(received, "received", mockLog)
   221  	if err == nil {
   222  		t.Error("unpacking anonymous event is not supported")
   223  	}
   224  	if err.Error() != "no event signature" {
   225  		t.Errorf("expected error 'no event signature', got '%s'", err)
   226  	}
   227  }
   228  
   229  func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
   230  	t.Parallel()
   231  	sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
   232  	if err != nil {
   233  		t.Fatal(err)
   234  	}
   235  	hash := crypto.Keccak256Hash(sliceBytes)
   236  	topics := []common.Hash{
   237  		crypto.Keccak256Hash([]byte("received(string[],address,uint256,bytes)")),
   238  		hash,
   239  	}
   240  	mockLog := newMockLog(topics, common.HexToHash("0x0"))
   241  
   242  	abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
   243  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   244  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   245  
   246  	expectedReceivedMap := map[string]interface{}{
   247  		"names":  hash,
   248  		"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   249  		"amount": big.NewInt(1),
   250  		"memo":   []byte{88},
   251  	}
   252  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   253  }
   254  
   255  func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
   256  	t.Parallel()
   257  	arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
   258  	if err != nil {
   259  		t.Fatal(err)
   260  	}
   261  	hash := crypto.Keccak256Hash(arrBytes)
   262  	topics := []common.Hash{
   263  		crypto.Keccak256Hash([]byte("received(address[2],address,uint256,bytes)")),
   264  		hash,
   265  	}
   266  	mockLog := newMockLog(topics, common.HexToHash("0x0"))
   267  
   268  	abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
   269  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   270  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   271  
   272  	expectedReceivedMap := map[string]interface{}{
   273  		"addresses": hash,
   274  		"sender":    common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   275  		"amount":    big.NewInt(1),
   276  		"memo":      []byte{88},
   277  	}
   278  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   279  }
   280  
   281  func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
   282  	t.Parallel()
   283  	mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
   284  	addrBytes := mockAddress.Bytes()
   285  	hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
   286  	functionSelector := hash[:4]
   287  	functionTyBytes := append(addrBytes, functionSelector...)
   288  	var functionTy [24]byte
   289  	copy(functionTy[:], functionTyBytes[0:24])
   290  	topics := []common.Hash{
   291  		crypto.Keccak256Hash([]byte("received(function,address,uint256,bytes)")),
   292  		common.BytesToHash(functionTyBytes),
   293  	}
   294  	mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
   295  	abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
   296  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   297  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   298  
   299  	expectedReceivedMap := map[string]interface{}{
   300  		"function": functionTy,
   301  		"sender":   common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   302  		"amount":   big.NewInt(1),
   303  		"memo":     []byte{88},
   304  	}
   305  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   306  }
   307  
   308  func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
   309  	t.Parallel()
   310  	bytes := []byte{1, 2, 3, 4, 5}
   311  	hash := crypto.Keccak256Hash(bytes)
   312  	topics := []common.Hash{
   313  		crypto.Keccak256Hash([]byte("received(bytes,address,uint256,bytes)")),
   314  		hash,
   315  	}
   316  	mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
   317  
   318  	abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
   319  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   320  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   321  
   322  	expectedReceivedMap := map[string]interface{}{
   323  		"content": hash,
   324  		"sender":  common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   325  		"amount":  big.NewInt(1),
   326  		"memo":    []byte{88},
   327  	}
   328  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   329  }
   330  
   331  func TestTransactGasFee(t *testing.T) {
   332  	t.Parallel()
   333  	assert := assert.New(t)
   334  
   335  	// GasTipCap and GasFeeCap
   336  	// When opts.GasTipCap and opts.GasFeeCap are nil
   337  	mt := &mockTransactor{baseFee: big.NewInt(100), gasTipCap: big.NewInt(5)}
   338  	bc := bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil)
   339  	opts := &bind.TransactOpts{Signer: mockSign}
   340  	tx, err := bc.Transact(opts, "")
   341  	assert.Nil(err)
   342  	assert.Equal(big.NewInt(5), tx.GasTipCap())
   343  	assert.Equal(big.NewInt(205), tx.GasFeeCap())
   344  	assert.Nil(opts.GasTipCap)
   345  	assert.Nil(opts.GasFeeCap)
   346  	assert.True(mt.suggestGasTipCapCalled)
   347  
   348  	// Second call to Transact should use latest suggested GasTipCap
   349  	mt.gasTipCap = big.NewInt(6)
   350  	mt.suggestGasTipCapCalled = false
   351  	tx, err = bc.Transact(opts, "")
   352  	assert.Nil(err)
   353  	assert.Equal(big.NewInt(6), tx.GasTipCap())
   354  	assert.Equal(big.NewInt(206), tx.GasFeeCap())
   355  	assert.True(mt.suggestGasTipCapCalled)
   356  
   357  	// GasPrice
   358  	// When opts.GasPrice is nil
   359  	mt = &mockTransactor{gasPrice: big.NewInt(5)}
   360  	bc = bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil)
   361  	opts = &bind.TransactOpts{Signer: mockSign}
   362  	tx, err = bc.Transact(opts, "")
   363  	assert.Nil(err)
   364  	assert.Equal(big.NewInt(5), tx.GasPrice())
   365  	assert.Nil(opts.GasPrice)
   366  	assert.True(mt.suggestGasPriceCalled)
   367  
   368  	// Second call to Transact should use latest suggested GasPrice
   369  	mt.gasPrice = big.NewInt(6)
   370  	mt.suggestGasPriceCalled = false
   371  	tx, err = bc.Transact(opts, "")
   372  	assert.Nil(err)
   373  	assert.Equal(big.NewInt(6), tx.GasPrice())
   374  	assert.True(mt.suggestGasPriceCalled)
   375  }
   376  
   377  func unpackAndCheck(t *testing.T, bc *bind.BoundContract, expected map[string]interface{}, mockLog types.Log) {
   378  	received := make(map[string]interface{})
   379  	if err := bc.UnpackLogIntoMap(received, "received", mockLog); err != nil {
   380  		t.Error(err)
   381  	}
   382  
   383  	if len(received) != len(expected) {
   384  		t.Fatalf("unpacked map length %v not equal expected length of %v", len(received), len(expected))
   385  	}
   386  	for name, elem := range expected {
   387  		if !reflect.DeepEqual(elem, received[name]) {
   388  			t.Errorf("field %v does not match expected, want %v, got %v", name, elem, received[name])
   389  		}
   390  	}
   391  }
   392  
   393  func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
   394  	return types.Log{
   395  		Address:     common.HexToAddress("0x0"),
   396  		Topics:      topics,
   397  		Data:        hexutil.MustDecode(hexData),
   398  		BlockNumber: uint64(26),
   399  		TxHash:      txHash,
   400  		TxIndex:     111,
   401  		BlockHash:   common.BytesToHash([]byte{1, 2, 3, 4, 5}),
   402  		Index:       7,
   403  		Removed:     false,
   404  	}
   405  }
   406  
   407  func TestCall(t *testing.T) {
   408  	t.Parallel()
   409  	var method, methodWithArg = "something", "somethingArrrrg"
   410  	tests := []struct {
   411  		name, method string
   412  		opts         *bind.CallOpts
   413  		mc           bind.ContractCaller
   414  		results      *[]interface{}
   415  		wantErr      bool
   416  		wantErrExact error
   417  	}{{
   418  		name: "ok not pending",
   419  		mc: &mockCaller{
   420  			codeAtBytes: []byte{0},
   421  		},
   422  		method: method,
   423  	}, {
   424  		name: "ok pending",
   425  		mc: &mockPendingCaller{
   426  			pendingCodeAtBytes: []byte{0},
   427  		},
   428  		opts: &bind.CallOpts{
   429  			Pending: true,
   430  		},
   431  		method: method,
   432  	}, {
   433  		name: "ok hash",
   434  		mc: &mockBlockHashCaller{
   435  			codeAtHashBytes: []byte{0},
   436  		},
   437  		opts: &bind.CallOpts{
   438  			BlockHash: common.Hash{0xaa},
   439  		},
   440  		method: method,
   441  	}, {
   442  		name:    "pack error, no method",
   443  		mc:      new(mockCaller),
   444  		method:  "else",
   445  		wantErr: true,
   446  	}, {
   447  		name: "interface error, pending but not a PendingContractCaller",
   448  		mc:   new(mockCaller),
   449  		opts: &bind.CallOpts{
   450  			Pending: true,
   451  		},
   452  		method:       method,
   453  		wantErrExact: bind.ErrNoPendingState,
   454  	}, {
   455  		name: "interface error, blockHash but not a BlockHashContractCaller",
   456  		mc:   new(mockCaller),
   457  		opts: &bind.CallOpts{
   458  			BlockHash: common.Hash{0xaa},
   459  		},
   460  		method:       method,
   461  		wantErrExact: bind.ErrNoBlockHashState,
   462  	}, {
   463  		name: "pending call canceled",
   464  		mc: &mockPendingCaller{
   465  			pendingCallContractErr: context.DeadlineExceeded,
   466  		},
   467  		opts: &bind.CallOpts{
   468  			Pending: true,
   469  		},
   470  		method:       method,
   471  		wantErrExact: context.DeadlineExceeded,
   472  	}, {
   473  		name: "pending code at error",
   474  		mc: &mockPendingCaller{
   475  			pendingCodeAtErr: errors.New(""),
   476  		},
   477  		opts: &bind.CallOpts{
   478  			Pending: true,
   479  		},
   480  		method:  method,
   481  		wantErr: true,
   482  	}, {
   483  		name: "no pending code at",
   484  		mc:   new(mockPendingCaller),
   485  		opts: &bind.CallOpts{
   486  			Pending: true,
   487  		},
   488  		method:       method,
   489  		wantErrExact: bind.ErrNoCode,
   490  	}, {
   491  		name: "call contract error",
   492  		mc: &mockCaller{
   493  			callContractErr: context.DeadlineExceeded,
   494  		},
   495  		method:       method,
   496  		wantErrExact: context.DeadlineExceeded,
   497  	}, {
   498  		name: "code at error",
   499  		mc: &mockCaller{
   500  			codeAtErr: errors.New(""),
   501  		},
   502  		method:  method,
   503  		wantErr: true,
   504  	}, {
   505  		name:         "no code at",
   506  		mc:           new(mockCaller),
   507  		method:       method,
   508  		wantErrExact: bind.ErrNoCode,
   509  	}, {
   510  		name: "call contract at hash error",
   511  		mc: &mockBlockHashCaller{
   512  			callContractAtHashErr: context.DeadlineExceeded,
   513  		},
   514  		opts: &bind.CallOpts{
   515  			BlockHash: common.Hash{0xaa},
   516  		},
   517  		method:       method,
   518  		wantErrExact: context.DeadlineExceeded,
   519  	}, {
   520  		name: "code at error",
   521  		mc: &mockBlockHashCaller{
   522  			codeAtHashErr: errors.New(""),
   523  		},
   524  		opts: &bind.CallOpts{
   525  			BlockHash: common.Hash{0xaa},
   526  		},
   527  		method:  method,
   528  		wantErr: true,
   529  	}, {
   530  		name: "no code at hash",
   531  		mc:   new(mockBlockHashCaller),
   532  		opts: &bind.CallOpts{
   533  			BlockHash: common.Hash{0xaa},
   534  		},
   535  		method:       method,
   536  		wantErrExact: bind.ErrNoCode,
   537  	}, {
   538  		name: "unpack error missing arg",
   539  		mc: &mockCaller{
   540  			codeAtBytes: []byte{0},
   541  		},
   542  		method:  methodWithArg,
   543  		wantErr: true,
   544  	}, {
   545  		name: "interface unpack error",
   546  		mc: &mockCaller{
   547  			codeAtBytes: []byte{0},
   548  		},
   549  		method:  method,
   550  		results: &[]interface{}{0},
   551  		wantErr: true,
   552  	}}
   553  	for _, test := range tests {
   554  		bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
   555  			Methods: map[string]abi.Method{
   556  				method: {
   557  					Name:    method,
   558  					Outputs: abi.Arguments{},
   559  				},
   560  				methodWithArg: {
   561  					Name:    methodWithArg,
   562  					Outputs: abi.Arguments{abi.Argument{}},
   563  				},
   564  			},
   565  		}, test.mc, nil, nil)
   566  		err := bc.Call(test.opts, test.results, test.method)
   567  		if test.wantErr || test.wantErrExact != nil {
   568  			if err == nil {
   569  				t.Fatalf("%q expected error", test.name)
   570  			}
   571  			if test.wantErrExact != nil && !errors.Is(err, test.wantErrExact) {
   572  				t.Fatalf("%q expected error %q but got %q", test.name, test.wantErrExact, err)
   573  			}
   574  			continue
   575  		}
   576  		if err != nil {
   577  			t.Fatalf("%q unexpected error: %v", test.name, err)
   578  		}
   579  	}
   580  }
   581  
   582  // TestCrashers contains some strings which previously caused the abi codec to crash.
   583  func TestCrashers(t *testing.T) {
   584  	t.Parallel()
   585  	abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
   586  	abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
   587  	abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
   588  	abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"foo.Bar"}]}]}]`))
   589  }