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