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 }