mirror of
https://github.com/trezor/blockbook.git
synced 2026-02-20 00:51:39 +01:00
eth_call batch integration tests for avax,op,base,bsc
This commit is contained in:
31
bchain/coins/avalanche/contract_batch_integration_test.go
Normal file
31
bchain/coins/avalanche/contract_batch_integration_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build integration
|
||||
|
||||
package avalanche
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/trezor/blockbook/bchain/coins"
|
||||
)
|
||||
|
||||
const defaultAvaxRpcURL = "http://localhost:8098/ext/bc/C/rpc"
|
||||
|
||||
func TestAvalancheErc20ContractBalancesIntegration(t *testing.T) {
|
||||
coins.RunERC20BatchBalanceTest(t, coins.ERC20BatchCase{
|
||||
Name: "avalanche",
|
||||
RPCURL: defaultAvaxRpcURL,
|
||||
// Token-rich address on Avalanche C-Chain (balanceOf works for any address).
|
||||
Addr: common.HexToAddress("0x60aE616a2155Ee3d9A68541Ba4544862310933d4"),
|
||||
Contracts: []common.Address{
|
||||
common.HexToAddress("0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7"), // WAVAX
|
||||
common.HexToAddress("0xA7D7079b0FEAD91F3e65f86E8915Cb59c1a4C664"), // USDC.e
|
||||
common.HexToAddress("0xc7198437980c041c805A1EDcbA50c1Ce5db95118"), // USDT.e
|
||||
common.HexToAddress("0xd586e7f844cea2f87f50152665bcbc2c279d8d70"), // DAI.e
|
||||
common.HexToAddress("0x49D5c2BdFfac6Ce2BFdB6640F4F80f226bc10bAB"), // WETH.e
|
||||
common.HexToAddress("0x60781C2586D68229fde47564546784ab3fACA982"), // PNG
|
||||
},
|
||||
BatchSize: 200,
|
||||
SkipUnavailable: true,
|
||||
})
|
||||
}
|
||||
28
bchain/coins/base/contract_batch_integration_test.go
Normal file
28
bchain/coins/base/contract_batch_integration_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
//go:build integration
|
||||
|
||||
package base_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/trezor/blockbook/bchain/coins"
|
||||
)
|
||||
|
||||
const defaultBaseRpcURL = "ws://localhost:8309"
|
||||
|
||||
func TestBaseErc20ContractBalancesIntegration(t *testing.T) {
|
||||
coins.RunERC20BatchBalanceTest(t, coins.ERC20BatchCase{
|
||||
Name: "base",
|
||||
RPCURL: defaultBaseRpcURL,
|
||||
Addr: common.HexToAddress("0x242E2d70d3AdC00a9eF23CeD6E88811fCefCA788"),
|
||||
Contracts: []common.Address{
|
||||
common.HexToAddress("0x4200000000000000000000000000000000000006"), // WETH
|
||||
common.HexToAddress("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"), // USDC
|
||||
common.HexToAddress("0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb"), // DAI
|
||||
common.HexToAddress("0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22"), // cbETH
|
||||
},
|
||||
BatchSize: 200,
|
||||
SkipUnavailable: true,
|
||||
})
|
||||
}
|
||||
29
bchain/coins/bsc/contract_batch_integration_test.go
Normal file
29
bchain/coins/bsc/contract_batch_integration_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
//go:build integration
|
||||
|
||||
package bsc_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/trezor/blockbook/bchain/coins"
|
||||
)
|
||||
|
||||
const defaultBscRpcURL = "ws://localhost:8064"
|
||||
|
||||
func TestBNBSmartChainErc20ContractBalancesIntegration(t *testing.T) {
|
||||
coins.RunERC20BatchBalanceTest(t, coins.ERC20BatchCase{
|
||||
Name: "bsc",
|
||||
RPCURL: defaultBscRpcURL,
|
||||
Addr: common.HexToAddress("0x21d45650db732cE5dF77685d6021d7D5d1da807f"),
|
||||
Contracts: []common.Address{
|
||||
common.HexToAddress("0xBB4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"), // WBNB
|
||||
common.HexToAddress("0x55d398326f99059fF775485246999027B3197955"), // USDT
|
||||
common.HexToAddress("0xe9e7CEA3Dedca5984780Bafc599bd69ADd087d56"), // BUSD
|
||||
common.HexToAddress("0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d"), // USDC
|
||||
common.HexToAddress("0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3"), // DAI
|
||||
},
|
||||
BatchSize: 200,
|
||||
SkipUnavailable: true,
|
||||
})
|
||||
}
|
||||
133
bchain/coins/erc20_batch_integration.go
Normal file
133
bchain/coins/erc20_batch_integration.go
Normal file
@@ -0,0 +1,133 @@
|
||||
//go:build integration
|
||||
|
||||
package coins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/trezor/blockbook/bchain"
|
||||
"github.com/trezor/blockbook/bchain/coins/eth"
|
||||
)
|
||||
|
||||
const defaultBatchSize = 200
|
||||
|
||||
type ERC20BatchCase struct {
|
||||
Name string
|
||||
RPCURL string
|
||||
Addr common.Address
|
||||
Contracts []common.Address
|
||||
BatchSize int
|
||||
SkipUnavailable bool
|
||||
}
|
||||
|
||||
func RunERC20BatchBalanceTest(t *testing.T, tc ERC20BatchCase) {
|
||||
t.Helper()
|
||||
if tc.BatchSize <= 0 {
|
||||
tc.BatchSize = defaultBatchSize
|
||||
}
|
||||
rc, _, err := eth.OpenRPC(tc.RPCURL)
|
||||
if err != nil {
|
||||
handleRPCError(t, tc, fmt.Errorf("rpc dial error: %w", err))
|
||||
return
|
||||
}
|
||||
t.Cleanup(func() { rc.Close() })
|
||||
|
||||
rpcClient := ð.EthereumRPC{
|
||||
RPC: rc,
|
||||
Timeout: 15 * time.Second,
|
||||
ChainConfig: ð.Configuration{Erc20BatchSize: tc.BatchSize},
|
||||
}
|
||||
if err := verifyBatchBalances(rpcClient, tc.Addr, tc.Contracts); err != nil {
|
||||
handleRPCError(t, tc, err)
|
||||
return
|
||||
}
|
||||
chunkedContracts := expandContracts(tc.Contracts, tc.BatchSize+1)
|
||||
if err := verifyBatchBalances(rpcClient, tc.Addr, chunkedContracts); err != nil {
|
||||
handleRPCError(t, tc, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleRPCError(t *testing.T, tc ERC20BatchCase, err error) {
|
||||
t.Helper()
|
||||
if tc.SkipUnavailable && isRPCUnavailable(err) {
|
||||
t.Skipf("WARN: %s RPC not available: %v", tc.Name, err)
|
||||
return
|
||||
}
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
func expandContracts(contracts []common.Address, minLen int) []common.Address {
|
||||
if len(contracts) >= minLen {
|
||||
return contracts
|
||||
}
|
||||
out := make([]common.Address, 0, minLen)
|
||||
for len(out) < minLen {
|
||||
out = append(out, contracts...)
|
||||
}
|
||||
if len(out) > minLen {
|
||||
out = out[:minLen]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func verifyBatchBalances(rpcClient *eth.EthereumRPC, addr common.Address, contracts []common.Address) error {
|
||||
if len(contracts) == 0 {
|
||||
return errors.New("no contracts to query")
|
||||
}
|
||||
contractDescs := make([]bchain.AddressDescriptor, len(contracts))
|
||||
for i, c := range contracts {
|
||||
contractDescs[i] = bchain.AddressDescriptor(c.Bytes())
|
||||
}
|
||||
addrDesc := bchain.AddressDescriptor(addr.Bytes())
|
||||
balances, err := rpcClient.EthereumTypeGetErc20ContractBalances(addrDesc, contractDescs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("batch balances error: %w", err)
|
||||
}
|
||||
if len(balances) != len(contractDescs) {
|
||||
return fmt.Errorf("expected %d balances, got %d", len(contractDescs), len(balances))
|
||||
}
|
||||
for i, contractDesc := range contractDescs {
|
||||
single, err := rpcClient.EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("single balance error for %s: %w", contracts[i].Hex(), err)
|
||||
}
|
||||
if balances[i] == nil {
|
||||
return fmt.Errorf("batch balance missing for %s", contracts[i].Hex())
|
||||
}
|
||||
if balances[i].Cmp(single) != 0 {
|
||||
return fmt.Errorf("balance mismatch for %s: batch=%s single=%s", contracts[i].Hex(), balances[i].String(), single.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isRPCUnavailable(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return true
|
||||
}
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) {
|
||||
return true
|
||||
}
|
||||
msg := strings.ToLower(err.Error())
|
||||
switch {
|
||||
case strings.Contains(msg, "context deadline exceeded"),
|
||||
strings.Contains(msg, "connection refused"),
|
||||
strings.Contains(msg, "no such host"),
|
||||
strings.Contains(msg, "i/o timeout"),
|
||||
strings.Contains(msg, "timeout"):
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,78 +1,29 @@
|
||||
//go:build integration
|
||||
|
||||
package eth
|
||||
package eth_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/trezor/blockbook/bchain"
|
||||
"github.com/trezor/blockbook/bchain/coins"
|
||||
)
|
||||
|
||||
const defaultEthRpcURL = "http://naked:8545"
|
||||
|
||||
func verifyBatchBalances(t *testing.T, rpcClient *EthereumRPC, addr common.Address, contracts []common.Address) {
|
||||
t.Helper()
|
||||
contractDescs := make([]bchain.AddressDescriptor, len(contracts))
|
||||
for i, c := range contracts {
|
||||
contractDescs[i] = bchain.AddressDescriptor(c.Bytes())
|
||||
}
|
||||
addrDesc := bchain.AddressDescriptor(addr.Bytes())
|
||||
balances, err := rpcClient.EthereumTypeGetErc20ContractBalances(addrDesc, contractDescs)
|
||||
if err != nil {
|
||||
t.Fatalf("batch balances error: %v", err)
|
||||
}
|
||||
if len(balances) != len(contractDescs) {
|
||||
t.Fatalf("expected %d balances, got %d", len(contractDescs), len(balances))
|
||||
}
|
||||
for i, contractDesc := range contractDescs {
|
||||
single, err := rpcClient.EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc)
|
||||
if err != nil {
|
||||
t.Fatalf("single balance error for %s: %v", contracts[i].Hex(), err)
|
||||
}
|
||||
if balances[i] == nil {
|
||||
t.Fatalf("batch balance missing for %s", contracts[i].Hex())
|
||||
}
|
||||
if balances[i].Cmp(single) != 0 {
|
||||
t.Fatalf("balance mismatch for %s: batch=%s single=%s", contracts[i].Hex(), balances[i].String(), single.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
const defaultEthRpcURL = "http://localhost:8545"
|
||||
|
||||
func TestEthereumTypeGetErc20ContractBalancesIntegration(t *testing.T) {
|
||||
rpcURL := defaultEthRpcURL
|
||||
rc, _, err := OpenRPC(rpcURL)
|
||||
if err != nil {
|
||||
t.Skipf("skipping: cannot connect to RPC at %s: %v", rpcURL, err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// Use stable mainnet ERC20 contracts and a well-known EOA.
|
||||
addr := common.HexToAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
|
||||
baseContracts := []common.Address{
|
||||
common.HexToAddress("0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), // USDC
|
||||
common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7"), // USDT
|
||||
common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
||||
}
|
||||
|
||||
rpcClient := &EthereumRPC{
|
||||
RPC: rc,
|
||||
Timeout: 15 * time.Second,
|
||||
}
|
||||
verifyBatchBalances(t, rpcClient, addr, baseContracts)
|
||||
|
||||
chunkedContracts := []common.Address{
|
||||
common.HexToAddress("0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), // USDC
|
||||
common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7"), // USDT
|
||||
common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
||||
common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"), // DAI
|
||||
}
|
||||
rpcClientChunked := &EthereumRPC{
|
||||
RPC: rc,
|
||||
Timeout: 15 * time.Second,
|
||||
ChainConfig: &Configuration{Erc20BatchSize: 2},
|
||||
}
|
||||
verifyBatchBalances(t, rpcClientChunked, addr, chunkedContracts)
|
||||
coins.RunERC20BatchBalanceTest(t, coins.ERC20BatchCase{
|
||||
Name: "ethereum",
|
||||
RPCURL: defaultEthRpcURL,
|
||||
// Token-rich EOA (CEX hot wallet) used as a stable address reference.
|
||||
Addr: common.HexToAddress("0x28C6c06298d514Db089934071355E5743bf21d60"),
|
||||
Contracts: []common.Address{
|
||||
common.HexToAddress("0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), // USDC
|
||||
common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7"), // USDT
|
||||
common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
||||
common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"), // DAI
|
||||
},
|
||||
BatchSize: 200,
|
||||
SkipUnavailable: false,
|
||||
})
|
||||
}
|
||||
|
||||
29
bchain/coins/optimism/contract_batch_integration_test.go
Normal file
29
bchain/coins/optimism/contract_batch_integration_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
//go:build integration
|
||||
|
||||
package optimism_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/trezor/blockbook/bchain/coins"
|
||||
)
|
||||
|
||||
const defaultOptimismRpcURL = "ws://localhost:8200"
|
||||
|
||||
func TestOptimismErc20ContractBalancesIntegration(t *testing.T) {
|
||||
coins.RunERC20BatchBalanceTest(t, coins.ERC20BatchCase{
|
||||
Name: "optimism",
|
||||
RPCURL: defaultOptimismRpcURL,
|
||||
Addr: common.HexToAddress("0xDF90C9B995a3b10A5b8570a47101e6c6a29eb945"),
|
||||
Contracts: []common.Address{
|
||||
common.HexToAddress("0x4200000000000000000000000000000000000006"), // WETH
|
||||
common.HexToAddress("0x7F5c764cBc14f9669B88837ca1490cCa17c31607"), // USDC
|
||||
common.HexToAddress("0x94b008aa00579c1307b0ef2c499ad98a8ce58e58"), // USDT
|
||||
common.HexToAddress("0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1"), // DAI
|
||||
common.HexToAddress("0x4200000000000000000000000000000000000042"), // OP
|
||||
},
|
||||
BatchSize: 200,
|
||||
SkipUnavailable: true,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user