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