github.com/ethxdao/go-ethereum@v0.0.0-20221218102228-5ae34a9cc189/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/ethxdao/go-ethereum/accounts/abi"
    28  	"github.com/ethxdao/go-ethereum/accounts/abi/bind"
    29  	"github.com/ethxdao/go-ethereum/common"
    30  	"github.com/ethxdao/go-ethereum/common/hexutil"
    31  	"github.com/ethxdao/go-ethereum/core/types"
    32  	"github.com/ethxdao/go-ethereum/crypto"
    33  	"github.com/ethxdao/go-ethereum/rlp"
    34  	"github.com/stretchr/testify/assert"
    35  )
    36  
    37  func mockSign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { return tx, nil }
    38  
    39  type mockTransactor struct {
    40  	baseFee                *big.Int
    41  	gasTipCap              *big.Int
    42  	gasPrice               *big.Int
    43  	suggestGasTipCapCalled bool
    44  	suggestGasPriceCalled  bool
    45  }
    46  
    47  func (mt *mockTransactor) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
    48  	return &types.Header{BaseFee: mt.baseFee}, nil
    49  }
    50  
    51  func (mt *mockTransactor) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
    52  	return []byte{1}, nil
    53  }
    54  
    55  func (mt *mockTransactor) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
    56  	return 0, nil
    57  }
    58  
    59  func (mt *mockTransactor) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
    60  	mt.suggestGasPriceCalled = true
    61  	return mt.gasPrice, nil
    62  }
    63  
    64  func (mt *mockTransactor) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
    65  	mt.suggestGasTipCapCalled = true
    66  	return mt.gasTipCap, nil
    67  }
    68  
    69  func (mt *mockTransactor) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
    70  	return 0, nil
    71  }
    72  
    73  func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transaction) error {
    74  	return nil
    75  }
    76  
    77  type mockCaller struct {
    78  	codeAtBlockNumber       *big.Int
    79  	callContractBlockNumber *big.Int
    80  	callContractBytes       []byte
    81  	callContractErr         error
    82  	codeAtBytes             []byte
    83  	codeAtErr               error
    84  }
    85  
    86  func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
    87  	mc.codeAtBlockNumber = blockNumber
    88  	return mc.codeAtBytes, mc.codeAtErr
    89  }
    90  
    91  func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
    92  	mc.callContractBlockNumber = blockNumber
    93  	return mc.callContractBytes, mc.callContractErr
    94  }
    95  
    96  type mockPendingCaller struct {
    97  	*mockCaller
    98  	pendingCodeAtBytes        []byte
    99  	pendingCodeAtErr          error
   100  	pendingCodeAtCalled       bool
   101  	pendingCallContractCalled bool
   102  	pendingCallContractBytes  []byte
   103  	pendingCallContractErr    error
   104  }
   105  
   106  func (mc *mockPendingCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
   107  	mc.pendingCodeAtCalled = true
   108  	return mc.pendingCodeAtBytes, mc.pendingCodeAtErr
   109  }
   110  
   111  func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
   112  	mc.pendingCallContractCalled = true
   113  	return mc.pendingCallContractBytes, mc.pendingCallContractErr
   114  }
   115  
   116  func TestPassingBlockNumber(t *testing.T) {
   117  	mc := &mockPendingCaller{
   118  		mockCaller: &mockCaller{
   119  			codeAtBytes: []byte{1, 2, 3},
   120  		},
   121  	}
   122  
   123  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
   124  		Methods: map[string]abi.Method{
   125  			"something": {
   126  				Name:    "something",
   127  				Outputs: abi.Arguments{},
   128  			},
   129  		},
   130  	}, mc, nil, nil)
   131  
   132  	blockNumber := big.NewInt(42)
   133  
   134  	bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, nil, "something")
   135  
   136  	if mc.callContractBlockNumber != blockNumber {
   137  		t.Fatalf("CallContract() was not passed the block number")
   138  	}
   139  
   140  	if mc.codeAtBlockNumber != blockNumber {
   141  		t.Fatalf("CodeAt() was not passed the block number")
   142  	}
   143  
   144  	bc.Call(&bind.CallOpts{}, nil, "something")
   145  
   146  	if mc.callContractBlockNumber != nil {
   147  		t.Fatalf("CallContract() was passed a block number when it should not have been")
   148  	}
   149  
   150  	if mc.codeAtBlockNumber != nil {
   151  		t.Fatalf("CodeAt() was passed a block number when it should not have been")
   152  	}
   153  
   154  	bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, nil, "something")
   155  
   156  	if !mc.pendingCallContractCalled {
   157  		t.Fatalf("CallContract() was not passed the block number")
   158  	}
   159  
   160  	if !mc.pendingCodeAtCalled {
   161  		t.Fatalf("CodeAt() was not passed the block number")
   162  	}
   163  }
   164  
   165  const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
   166  
   167  func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
   168  	hash := crypto.Keccak256Hash([]byte("testName"))
   169  	topics := []common.Hash{
   170  		crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
   171  		hash,
   172  	}
   173  	mockLog := newMockLog(topics, common.HexToHash("0x0"))
   174  
   175  	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"}]`
   176  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   177  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   178  
   179  	expectedReceivedMap := map[string]interface{}{
   180  		"name":   hash,
   181  		"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   182  		"amount": big.NewInt(1),
   183  		"memo":   []byte{88},
   184  	}
   185  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   186  }
   187  
   188  func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
   189  	sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
   190  	if err != nil {
   191  		t.Fatal(err)
   192  	}
   193  	hash := crypto.Keccak256Hash(sliceBytes)
   194  	topics := []common.Hash{
   195  		crypto.Keccak256Hash([]byte("received(string[],address,uint256,bytes)")),
   196  		hash,
   197  	}
   198  	mockLog := newMockLog(topics, common.HexToHash("0x0"))
   199  
   200  	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"}]`
   201  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   202  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   203  
   204  	expectedReceivedMap := map[string]interface{}{
   205  		"names":  hash,
   206  		"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   207  		"amount": big.NewInt(1),
   208  		"memo":   []byte{88},
   209  	}
   210  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   211  }
   212  
   213  func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
   214  	arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	hash := crypto.Keccak256Hash(arrBytes)
   219  	topics := []common.Hash{
   220  		crypto.Keccak256Hash([]byte("received(address[2],address,uint256,bytes)")),
   221  		hash,
   222  	}
   223  	mockLog := newMockLog(topics, common.HexToHash("0x0"))
   224  
   225  	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"}]`
   226  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   227  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   228  
   229  	expectedReceivedMap := map[string]interface{}{
   230  		"addresses": hash,
   231  		"sender":    common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   232  		"amount":    big.NewInt(1),
   233  		"memo":      []byte{88},
   234  	}
   235  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   236  }
   237  
   238  func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
   239  	mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
   240  	addrBytes := mockAddress.Bytes()
   241  	hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
   242  	functionSelector := hash[:4]
   243  	functionTyBytes := append(addrBytes, functionSelector...)
   244  	var functionTy [24]byte
   245  	copy(functionTy[:], functionTyBytes[0:24])
   246  	topics := []common.Hash{
   247  		crypto.Keccak256Hash([]byte("received(function,address,uint256,bytes)")),
   248  		common.BytesToHash(functionTyBytes),
   249  	}
   250  	mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
   251  	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"}]`
   252  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   253  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   254  
   255  	expectedReceivedMap := map[string]interface{}{
   256  		"function": functionTy,
   257  		"sender":   common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   258  		"amount":   big.NewInt(1),
   259  		"memo":     []byte{88},
   260  	}
   261  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   262  }
   263  
   264  func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
   265  	bytes := []byte{1, 2, 3, 4, 5}
   266  	hash := crypto.Keccak256Hash(bytes)
   267  	topics := []common.Hash{
   268  		crypto.Keccak256Hash([]byte("received(bytes,address,uint256,bytes)")),
   269  		hash,
   270  	}
   271  	mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"))
   272  
   273  	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"}]`
   274  	parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
   275  	bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
   276  
   277  	expectedReceivedMap := map[string]interface{}{
   278  		"content": hash,
   279  		"sender":  common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
   280  		"amount":  big.NewInt(1),
   281  		"memo":    []byte{88},
   282  	}
   283  	unpackAndCheck(t, bc, expectedReceivedMap, mockLog)
   284  }
   285  
   286  func TestTransactGasFee(t *testing.T) {
   287  	assert := assert.New(t)
   288  
   289  	// GasTipCap and GasFeeCap
   290  	// When opts.GasTipCap and opts.GasFeeCap are nil
   291  	mt := &mockTransactor{baseFee: big.NewInt(100), gasTipCap: big.NewInt(5)}
   292  	bc := bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil)
   293  	opts := &bind.TransactOpts{Signer: mockSign}
   294  	tx, err := bc.Transact(opts, "")
   295  	assert.Nil(err)
   296  	assert.Equal(big.NewInt(5), tx.GasTipCap())
   297  	assert.Equal(big.NewInt(205), tx.GasFeeCap())
   298  	assert.Nil(opts.GasTipCap)
   299  	assert.Nil(opts.GasFeeCap)
   300  	assert.True(mt.suggestGasTipCapCalled)
   301  
   302  	// Second call to Transact should use latest suggested GasTipCap
   303  	mt.gasTipCap = big.NewInt(6)
   304  	mt.suggestGasTipCapCalled = false
   305  	tx, err = bc.Transact(opts, "")
   306  	assert.Nil(err)
   307  	assert.Equal(big.NewInt(6), tx.GasTipCap())
   308  	assert.Equal(big.NewInt(206), tx.GasFeeCap())
   309  	assert.True(mt.suggestGasTipCapCalled)
   310  
   311  	// GasPrice
   312  	// When opts.GasPrice is nil
   313  	mt = &mockTransactor{gasPrice: big.NewInt(5)}
   314  	bc = bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil)
   315  	opts = &bind.TransactOpts{Signer: mockSign}
   316  	tx, err = bc.Transact(opts, "")
   317  	assert.Nil(err)
   318  	assert.Equal(big.NewInt(5), tx.GasPrice())
   319  	assert.Nil(opts.GasPrice)
   320  	assert.True(mt.suggestGasPriceCalled)
   321  
   322  	// Second call to Transact should use latest suggested GasPrice
   323  	mt.gasPrice = big.NewInt(6)
   324  	mt.suggestGasPriceCalled = false
   325  	tx, err = bc.Transact(opts, "")
   326  	assert.Nil(err)
   327  	assert.Equal(big.NewInt(6), tx.GasPrice())
   328  	assert.True(mt.suggestGasPriceCalled)
   329  }
   330  
   331  func unpackAndCheck(t *testing.T, bc *bind.BoundContract, expected map[string]interface{}, mockLog types.Log) {
   332  	received := make(map[string]interface{})
   333  	if err := bc.UnpackLogIntoMap(received, "received", mockLog); err != nil {
   334  		t.Error(err)
   335  	}
   336  
   337  	if len(received) != len(expected) {
   338  		t.Fatalf("unpacked map length %v not equal expected length of %v", len(received), len(expected))
   339  	}
   340  	for name, elem := range expected {
   341  		if !reflect.DeepEqual(elem, received[name]) {
   342  			t.Errorf("field %v does not match expected, want %v, got %v", name, elem, received[name])
   343  		}
   344  	}
   345  }
   346  
   347  func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
   348  	return types.Log{
   349  		Address:     common.HexToAddress("0x0"),
   350  		Topics:      topics,
   351  		Data:        hexutil.MustDecode(hexData),
   352  		BlockNumber: uint64(26),
   353  		TxHash:      txHash,
   354  		TxIndex:     111,
   355  		BlockHash:   common.BytesToHash([]byte{1, 2, 3, 4, 5}),
   356  		Index:       7,
   357  		Removed:     false,
   358  	}
   359  }
   360  
   361  func TestCall(t *testing.T) {
   362  	var method, methodWithArg = "something", "somethingArrrrg"
   363  	tests := []struct {
   364  		name, method string
   365  		opts         *bind.CallOpts
   366  		mc           bind.ContractCaller
   367  		results      *[]interface{}
   368  		wantErr      bool
   369  		wantErrExact error
   370  	}{{
   371  		name: "ok not pending",
   372  		mc: &mockCaller{
   373  			codeAtBytes: []byte{0},
   374  		},
   375  		method: method,
   376  	}, {
   377  		name: "ok pending",
   378  		mc: &mockPendingCaller{
   379  			pendingCodeAtBytes: []byte{0},
   380  		},
   381  		opts: &bind.CallOpts{
   382  			Pending: true,
   383  		},
   384  		method: method,
   385  	}, {
   386  		name:    "pack error, no method",
   387  		mc:      new(mockCaller),
   388  		method:  "else",
   389  		wantErr: true,
   390  	}, {
   391  		name: "interface error, pending but not a PendingContractCaller",
   392  		mc:   new(mockCaller),
   393  		opts: &bind.CallOpts{
   394  			Pending: true,
   395  		},
   396  		method:       method,
   397  		wantErrExact: bind.ErrNoPendingState,
   398  	}, {
   399  		name: "pending call canceled",
   400  		mc: &mockPendingCaller{
   401  			pendingCallContractErr: context.DeadlineExceeded,
   402  		},
   403  		opts: &bind.CallOpts{
   404  			Pending: true,
   405  		},
   406  		method:       method,
   407  		wantErrExact: context.DeadlineExceeded,
   408  	}, {
   409  		name: "pending code at error",
   410  		mc: &mockPendingCaller{
   411  			pendingCodeAtErr: errors.New(""),
   412  		},
   413  		opts: &bind.CallOpts{
   414  			Pending: true,
   415  		},
   416  		method:  method,
   417  		wantErr: true,
   418  	}, {
   419  		name: "no pending code at",
   420  		mc:   new(mockPendingCaller),
   421  		opts: &bind.CallOpts{
   422  			Pending: true,
   423  		},
   424  		method:       method,
   425  		wantErrExact: bind.ErrNoCode,
   426  	}, {
   427  		name: "call contract error",
   428  		mc: &mockCaller{
   429  			callContractErr: context.DeadlineExceeded,
   430  		},
   431  		method:       method,
   432  		wantErrExact: context.DeadlineExceeded,
   433  	}, {
   434  		name: "code at error",
   435  		mc: &mockCaller{
   436  			codeAtErr: errors.New(""),
   437  		},
   438  		method:  method,
   439  		wantErr: true,
   440  	}, {
   441  		name:         "no code at",
   442  		mc:           new(mockCaller),
   443  		method:       method,
   444  		wantErrExact: bind.ErrNoCode,
   445  	}, {
   446  		name: "unpack error missing arg",
   447  		mc: &mockCaller{
   448  			codeAtBytes: []byte{0},
   449  		},
   450  		method:  methodWithArg,
   451  		wantErr: true,
   452  	}, {
   453  		name: "interface unpack error",
   454  		mc: &mockCaller{
   455  			codeAtBytes: []byte{0},
   456  		},
   457  		method:  method,
   458  		results: &[]interface{}{0},
   459  		wantErr: true,
   460  	}}
   461  	for _, test := range tests {
   462  		bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
   463  			Methods: map[string]abi.Method{
   464  				method: {
   465  					Name:    method,
   466  					Outputs: abi.Arguments{},
   467  				},
   468  				methodWithArg: {
   469  					Name:    methodWithArg,
   470  					Outputs: abi.Arguments{abi.Argument{}},
   471  				},
   472  			},
   473  		}, test.mc, nil, nil)
   474  		err := bc.Call(test.opts, test.results, test.method)
   475  		if test.wantErr || test.wantErrExact != nil {
   476  			if err == nil {
   477  				t.Fatalf("%q expected error", test.name)
   478  			}
   479  			if test.wantErrExact != nil && !errors.Is(err, test.wantErrExact) {
   480  				t.Fatalf("%q expected error %q but got %q", test.name, test.wantErrExact, err)
   481  			}
   482  			continue
   483  		}
   484  		if err != nil {
   485  			t.Fatalf("%q unexpected error: %v", test.name, err)
   486  		}
   487  	}
   488  }
   489  
   490  // TestCrashers contains some strings which previously caused the abi codec to crash.
   491  func TestCrashers(t *testing.T) {
   492  	abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
   493  	abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
   494  	abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
   495  	abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"foo.Bar"}]}]}]`))
   496  }