feat: batch balance (#2833)

This commit is contained in:
Anthony Laibe 2022-09-09 08:58:36 +02:00 committed by GitHub
parent 0ecbb5e8d7
commit 48f678052c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 553 additions and 25 deletions

View File

@ -4,6 +4,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/status-im/status-go/contracts/directory" "github.com/status-im/status-go/contracts/directory"
"github.com/status-im/status-go/contracts/ethscan"
"github.com/status-im/status-go/contracts/registrar" "github.com/status-im/status-go/contracts/registrar"
"github.com/status-im/status-go/contracts/resolver" "github.com/status-im/status-go/contracts/resolver"
"github.com/status-im/status-go/contracts/snt" "github.com/status-im/status-go/contracts/snt"
@ -146,3 +147,20 @@ func (c *ContractMaker) NewDirectoryWithBackend(chainID uint64, backend *ethclie
backend, backend,
) )
} }
func (c *ContractMaker) NewEthScan(chainID uint64) (*ethscan.BalanceScanner, error) {
contractAddr, err := ethscan.ContractAddress(chainID)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return ethscan.NewBalanceScanner(
contractAddr,
backend,
)
}

View File

@ -0,0 +1,24 @@
package ethscan
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
var errorNotAvailableOnChainID = errors.New("not available for chainID")
var contractAddressByChainID = map[uint64]common.Address{
1: common.HexToAddress("0x08A8fDBddc160A7d5b957256b903dCAb1aE512C5"), // mainnet
3: common.HexToAddress("0x08A8fDBddc160A7d5b957256b903dCAb1aE512C5"), // ropsten
4: common.HexToAddress("0x08A8fDBddc160A7d5b957256b903dCAb1aE512C5"), // rinkeby
5: common.HexToAddress("0x08A8fDBddc160A7d5b957256b903dCAb1aE512C5"), // goerli
}
func ContractAddress(chainID uint64) (common.Address, error) {
addr, exists := contractAddressByChainID[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}

3
contracts/ethscan/doc.go Normal file
View File

@ -0,0 +1,3 @@
package ethscan
//go:generate abigen -sol ethscan.sol -pkg ethscan -out ethscan.go

View File

@ -0,0 +1,342 @@
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package ethscan
import (
"math/big"
"strings"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = big.NewInt
_ = strings.NewReader
_ = ethereum.NotFound
_ = bind.Bind
_ = common.Big1
_ = types.BloomLookup
_ = event.NewSubscription
)
// BalanceScannerResult is an auto generated low-level Go binding around an user-defined struct.
type BalanceScannerResult struct {
Success bool
Data []byte
}
// BalanceScannerABI is the input ABI used to generate the binding from.
const BalanceScannerABI = "[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"contracts\",\"type\":\"address[]\"},{\"internalType\":\"bytes[]\",\"name\":\"data\",\"type\":\"bytes[]\"},{\"internalType\":\"uint256\",\"name\":\"gas\",\"type\":\"uint256\"}],\"name\":\"call\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"structBalanceScanner.Result[]\",\"name\":\"results\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"contracts\",\"type\":\"address[]\"},{\"internalType\":\"bytes[]\",\"name\":\"data\",\"type\":\"bytes[]\"}],\"name\":\"call\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"structBalanceScanner.Result[]\",\"name\":\"results\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"addresses\",\"type\":\"address[]\"}],\"name\":\"etherBalances\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"structBalanceScanner.Result[]\",\"name\":\"results\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"addresses\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"tokenBalances\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"structBalanceScanner.Result[]\",\"name\":\"results\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"contracts\",\"type\":\"address[]\"}],\"name\":\"tokensBalance\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"structBalanceScanner.Result[]\",\"name\":\"results\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]"
// BalanceScannerFuncSigs maps the 4-byte function signature to its string representation.
var BalanceScannerFuncSigs = map[string]string{
"458b3a7c": "call(address[],bytes[])",
"36738374": "call(address[],bytes[],uint256)",
"dbdbb51b": "etherBalances(address[])",
"aad33091": "tokenBalances(address[],address)",
"e5da1b68": "tokensBalance(address,address[])",
}
// BalanceScanner is an auto generated Go binding around an Ethereum contract.
type BalanceScanner struct {
BalanceScannerCaller // Read-only binding to the contract
BalanceScannerTransactor // Write-only binding to the contract
BalanceScannerFilterer // Log filterer for contract events
}
// BalanceScannerCaller is an auto generated read-only Go binding around an Ethereum contract.
type BalanceScannerCaller struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// BalanceScannerTransactor is an auto generated write-only Go binding around an Ethereum contract.
type BalanceScannerTransactor struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// BalanceScannerFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
type BalanceScannerFilterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// BalanceScannerSession is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options.
type BalanceScannerSession struct {
Contract *BalanceScanner // Generic contract binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// BalanceScannerCallerSession is an auto generated read-only Go binding around an Ethereum contract,
// with pre-set call options.
type BalanceScannerCallerSession struct {
Contract *BalanceScannerCaller // Generic contract caller binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
}
// BalanceScannerTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
// with pre-set transact options.
type BalanceScannerTransactorSession struct {
Contract *BalanceScannerTransactor // Generic contract transactor binding to set the session for
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// BalanceScannerRaw is an auto generated low-level Go binding around an Ethereum contract.
type BalanceScannerRaw struct {
Contract *BalanceScanner // Generic contract binding to access the raw methods on
}
// BalanceScannerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
type BalanceScannerCallerRaw struct {
Contract *BalanceScannerCaller // Generic read-only contract binding to access the raw methods on
}
// BalanceScannerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
type BalanceScannerTransactorRaw struct {
Contract *BalanceScannerTransactor // Generic write-only contract binding to access the raw methods on
}
// NewBalanceScanner creates a new instance of BalanceScanner, bound to a specific deployed contract.
func NewBalanceScanner(address common.Address, backend bind.ContractBackend) (*BalanceScanner, error) {
contract, err := bindBalanceScanner(address, backend, backend, backend)
if err != nil {
return nil, err
}
return &BalanceScanner{BalanceScannerCaller: BalanceScannerCaller{contract: contract}, BalanceScannerTransactor: BalanceScannerTransactor{contract: contract}, BalanceScannerFilterer: BalanceScannerFilterer{contract: contract}}, nil
}
// NewBalanceScannerCaller creates a new read-only instance of BalanceScanner, bound to a specific deployed contract.
func NewBalanceScannerCaller(address common.Address, caller bind.ContractCaller) (*BalanceScannerCaller, error) {
contract, err := bindBalanceScanner(address, caller, nil, nil)
if err != nil {
return nil, err
}
return &BalanceScannerCaller{contract: contract}, nil
}
// NewBalanceScannerTransactor creates a new write-only instance of BalanceScanner, bound to a specific deployed contract.
func NewBalanceScannerTransactor(address common.Address, transactor bind.ContractTransactor) (*BalanceScannerTransactor, error) {
contract, err := bindBalanceScanner(address, nil, transactor, nil)
if err != nil {
return nil, err
}
return &BalanceScannerTransactor{contract: contract}, nil
}
// NewBalanceScannerFilterer creates a new log filterer instance of BalanceScanner, bound to a specific deployed contract.
func NewBalanceScannerFilterer(address common.Address, filterer bind.ContractFilterer) (*BalanceScannerFilterer, error) {
contract, err := bindBalanceScanner(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &BalanceScannerFilterer{contract: contract}, nil
}
// bindBalanceScanner binds a generic wrapper to an already deployed contract.
func bindBalanceScanner(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader(BalanceScannerABI))
if err != nil {
return nil, err
}
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_BalanceScanner *BalanceScannerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _BalanceScanner.Contract.BalanceScannerCaller.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_BalanceScanner *BalanceScannerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _BalanceScanner.Contract.BalanceScannerTransactor.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_BalanceScanner *BalanceScannerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _BalanceScanner.Contract.BalanceScannerTransactor.contract.Transact(opts, method, params...)
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_BalanceScanner *BalanceScannerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _BalanceScanner.Contract.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_BalanceScanner *BalanceScannerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _BalanceScanner.Contract.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_BalanceScanner *BalanceScannerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _BalanceScanner.Contract.contract.Transact(opts, method, params...)
}
// Call is a free data retrieval call binding the contract method 0x36738374.
//
// Solidity: function call(address[] contracts, bytes[] data, uint256 gas) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCaller) Call(opts *bind.CallOpts, contracts []common.Address, data [][]byte, gas *big.Int) ([]BalanceScannerResult, error) {
var out []interface{}
err := _BalanceScanner.contract.Call(opts, &out, "call", contracts, data, gas)
if err != nil {
return *new([]BalanceScannerResult), err
}
out0 := *abi.ConvertType(out[0], new([]BalanceScannerResult)).(*[]BalanceScannerResult)
return out0, err
}
// Call is a free data retrieval call binding the contract method 0x36738374.
//
// Solidity: function call(address[] contracts, bytes[] data, uint256 gas) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerSession) Call(contracts []common.Address, data [][]byte, gas *big.Int) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.Call(&_BalanceScanner.CallOpts, contracts, data, gas)
}
// Call is a free data retrieval call binding the contract method 0x36738374.
//
// Solidity: function call(address[] contracts, bytes[] data, uint256 gas) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCallerSession) Call(contracts []common.Address, data [][]byte, gas *big.Int) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.Call(&_BalanceScanner.CallOpts, contracts, data, gas)
}
// Call0 is a free data retrieval call binding the contract method 0x458b3a7c.
//
// Solidity: function call(address[] contracts, bytes[] data) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCaller) Call0(opts *bind.CallOpts, contracts []common.Address, data [][]byte) ([]BalanceScannerResult, error) {
var out []interface{}
err := _BalanceScanner.contract.Call(opts, &out, "call0", contracts, data)
if err != nil {
return *new([]BalanceScannerResult), err
}
out0 := *abi.ConvertType(out[0], new([]BalanceScannerResult)).(*[]BalanceScannerResult)
return out0, err
}
// Call0 is a free data retrieval call binding the contract method 0x458b3a7c.
//
// Solidity: function call(address[] contracts, bytes[] data) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerSession) Call0(contracts []common.Address, data [][]byte) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.Call0(&_BalanceScanner.CallOpts, contracts, data)
}
// Call0 is a free data retrieval call binding the contract method 0x458b3a7c.
//
// Solidity: function call(address[] contracts, bytes[] data) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCallerSession) Call0(contracts []common.Address, data [][]byte) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.Call0(&_BalanceScanner.CallOpts, contracts, data)
}
// EtherBalances is a free data retrieval call binding the contract method 0xdbdbb51b.
//
// Solidity: function etherBalances(address[] addresses) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCaller) EtherBalances(opts *bind.CallOpts, addresses []common.Address) ([]BalanceScannerResult, error) {
var out []interface{}
err := _BalanceScanner.contract.Call(opts, &out, "etherBalances", addresses)
if err != nil {
return *new([]BalanceScannerResult), err
}
out0 := *abi.ConvertType(out[0], new([]BalanceScannerResult)).(*[]BalanceScannerResult)
return out0, err
}
// EtherBalances is a free data retrieval call binding the contract method 0xdbdbb51b.
//
// Solidity: function etherBalances(address[] addresses) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerSession) EtherBalances(addresses []common.Address) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.EtherBalances(&_BalanceScanner.CallOpts, addresses)
}
// EtherBalances is a free data retrieval call binding the contract method 0xdbdbb51b.
//
// Solidity: function etherBalances(address[] addresses) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCallerSession) EtherBalances(addresses []common.Address) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.EtherBalances(&_BalanceScanner.CallOpts, addresses)
}
// TokenBalances is a free data retrieval call binding the contract method 0xaad33091.
//
// Solidity: function tokenBalances(address[] addresses, address token) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCaller) TokenBalances(opts *bind.CallOpts, addresses []common.Address, token common.Address) ([]BalanceScannerResult, error) {
var out []interface{}
err := _BalanceScanner.contract.Call(opts, &out, "tokenBalances", addresses, token)
if err != nil {
return *new([]BalanceScannerResult), err
}
out0 := *abi.ConvertType(out[0], new([]BalanceScannerResult)).(*[]BalanceScannerResult)
return out0, err
}
// TokenBalances is a free data retrieval call binding the contract method 0xaad33091.
//
// Solidity: function tokenBalances(address[] addresses, address token) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerSession) TokenBalances(addresses []common.Address, token common.Address) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.TokenBalances(&_BalanceScanner.CallOpts, addresses, token)
}
// TokenBalances is a free data retrieval call binding the contract method 0xaad33091.
//
// Solidity: function tokenBalances(address[] addresses, address token) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCallerSession) TokenBalances(addresses []common.Address, token common.Address) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.TokenBalances(&_BalanceScanner.CallOpts, addresses, token)
}
// TokensBalance is a free data retrieval call binding the contract method 0xe5da1b68.
//
// Solidity: function tokensBalance(address owner, address[] contracts) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCaller) TokensBalance(opts *bind.CallOpts, owner common.Address, contracts []common.Address) ([]BalanceScannerResult, error) {
var out []interface{}
err := _BalanceScanner.contract.Call(opts, &out, "tokensBalance", owner, contracts)
if err != nil {
return *new([]BalanceScannerResult), err
}
out0 := *abi.ConvertType(out[0], new([]BalanceScannerResult)).(*[]BalanceScannerResult)
return out0, err
}
// TokensBalance is a free data retrieval call binding the contract method 0xe5da1b68.
//
// Solidity: function tokensBalance(address owner, address[] contracts) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerSession) TokensBalance(owner common.Address, contracts []common.Address) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.TokensBalance(&_BalanceScanner.CallOpts, owner, contracts)
}
// TokensBalance is a free data retrieval call binding the contract method 0xe5da1b68.
//
// Solidity: function tokensBalance(address owner, address[] contracts) view returns((bool,bytes)[] results)
func (_BalanceScanner *BalanceScannerCallerSession) TokensBalance(owner common.Address, contracts []common.Address) ([]BalanceScannerResult, error) {
return _BalanceScanner.Contract.TokensBalance(&_BalanceScanner.CallOpts, owner, contracts)
}

View File

@ -0,0 +1,64 @@
/**
*Submitted for verification at Etherscan.io on 2021-04-07
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
/**
* @title An Ether or token balance scanner
* @author Maarten Zuidhoorn
* @author Luit Hollander
*/
abstract contract BalanceScanner {
struct Result {
bool success;
bytes data;
}
/**
* @notice Get the Ether balance for all addresses specified
* @param addresses The addresses to get the Ether balance for
* @return results The Ether balance for all addresses in the same order as specified
*/
function etherBalances(address[] calldata addresses) external virtual view returns (Result[] memory results);
/**
* @notice Get the ERC-20 token balance of `token` for all addresses specified
* @dev This does not check if the `token` address specified is actually an ERC-20 token
* @param addresses The addresses to get the token balance for
* @param token The address of the ERC-20 token contract
* @return results The token balance for all addresses in the same order as specified
*/
function tokenBalances(address[] calldata addresses, address token) external virtual view returns (Result[] memory results);
/**
* @notice Get the ERC-20 token balance from multiple contracts for a single owner
* @param owner The address of the token owner
* @param contracts The addresses of the ERC-20 token contracts
* @return results The token balances in the same order as the addresses specified
*/
function tokensBalance(address owner, address[] calldata contracts) external virtual view returns (Result[] memory results);
/**
* @notice Call multiple contracts with the provided arbitrary data
* @param contracts The contracts to call
* @param data The data to call the contracts with
* @return results The raw result of the contract calls
*/
function call(address[] calldata contracts, bytes[] calldata data) external virtual view returns (Result[] memory results);
/**
* @notice Call multiple contracts with the provided arbitrary data
* @param contracts The contracts to call
* @param data The data to call the contracts with
* @param gas The amount of gas to call the contracts with
* @return results The raw result of the contract calls
*/
function call(
address[] calldata contracts,
bytes[] calldata data,
uint256 gas
) public view virtual returns (Result[] memory results);
}

1
go.mod
View File

@ -259,7 +259,6 @@ require (
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
golang.org/x/tools v0.1.11 // indirect golang.org/x/tools v0.1.11 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect

View File

@ -12,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/contracts"
"github.com/status-im/status-go/contracts/ierc20" "github.com/status-im/status-go/contracts/ierc20"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/rpc/network" "github.com/status-im/status-go/rpc/network"
@ -20,6 +21,7 @@ import (
) )
var requestTimeout = 20 * time.Second var requestTimeout = 20 * time.Second
var nativeChainAddress = common.HexToAddress("0x")
type Token struct { type Token struct {
Address common.Address `json:"address"` Address common.Address `json:"address"`
@ -279,7 +281,7 @@ func (tm *TokenManager) getChainBalance(ctx context.Context, client *chain.Clien
} }
func (tm *TokenManager) getBalance(ctx context.Context, client *chain.Client, account common.Address, token common.Address) (*big.Int, error) { func (tm *TokenManager) getBalance(ctx context.Context, client *chain.Client, account common.Address, token common.Address) (*big.Int, error) {
if token == common.HexToAddress("0x") { if token == nativeChainAddress {
return tm.getChainBalance(ctx, client, account) return tm.getChainBalance(ctx, client, account)
} }
@ -292,42 +294,118 @@ func (tm *TokenManager) getBalances(parent context.Context, clients []*chain.Cli
mu sync.Mutex mu sync.Mutex
response = map[common.Address]map[common.Address]*hexutil.Big{} response = map[common.Address]map[common.Address]*hexutil.Big{}
) )
updateBalance := func(account common.Address, token common.Address, balance *big.Int) {
mu.Lock()
if _, ok := response[account]; !ok {
response[account] = map[common.Address]*hexutil.Big{}
}
if _, ok := response[account][token]; !ok {
zeroHex := hexutil.Big(*big.NewInt(0))
response[account][token] = &zeroHex
}
sum := big.NewInt(0).Add(response[account][token].ToInt(), balance)
sumHex := hexutil.Big(*sum)
response[account][token] = &sumHex
mu.Unlock()
}
contractMaker := contracts.ContractMaker{RPCClient: tm.RPCClient}
for clientIdx := range clients { for clientIdx := range clients {
for tokenIdx := range tokens { client := clients[clientIdx]
for accountIdx := range accounts { ethScanContract, err := contractMaker.NewEthScan(client.ChainID)
// Below, we set account, token and client from idx on purpose to avoid override
account := accounts[accountIdx] if err == nil {
token := tokens[tokenIdx] fetchChainBalance := false
client := clients[clientIdx] var tokenChunks [][]common.Address
chunkSize := 100
for i := 0; i < len(tokens); i += chunkSize {
end := i + chunkSize
if end > len(tokens) {
end = len(tokens)
}
tokenChunks = append(tokenChunks, tokens[i:end])
}
for _, token := range tokens {
if token == nativeChainAddress {
fetchChainBalance = true
}
}
if fetchChainBalance {
group.Add(func(parent context.Context) error { group.Add(func(parent context.Context) error {
ctx, cancel := context.WithTimeout(parent, requestTimeout) ctx, cancel := context.WithTimeout(parent, requestTimeout)
defer cancel() defer cancel()
balance, err := tm.getBalance(ctx, client, account, token) res, err := ethScanContract.EtherBalances(&bind.CallOpts{
Context: ctx,
// We don't want to return an error here and prevent }, accounts)
// the rest from completing
if err != nil { if err != nil {
log.Error("can't fetch erc20 token balance", "account", account, "token", token, "error", err) log.Error("can't fetch chain balance", err)
return nil return nil
} }
mu.Lock() for idx, account := range accounts {
if _, ok := response[account]; !ok { balance := new(big.Int)
response[account] = map[common.Address]*hexutil.Big{} balance.SetBytes(res[idx].Data)
updateBalance(account, common.HexToAddress("0x"), balance)
} }
if _, ok := response[account][token]; !ok {
zeroHex := hexutil.Big(*big.NewInt(0))
response[account][token] = &zeroHex
}
sum := big.NewInt(0).Add(response[account][token].ToInt(), balance)
sumHex := hexutil.Big(*sum)
response[account][token] = &sumHex
mu.Unlock()
return nil return nil
}) })
} }
for accountIdx := range accounts {
account := accounts[accountIdx]
for idx := range tokenChunks {
chunk := tokenChunks[idx]
group.Add(func(parent context.Context) error {
ctx, cancel := context.WithTimeout(parent, requestTimeout)
defer cancel()
res, err := ethScanContract.TokensBalance(&bind.CallOpts{
Context: ctx,
}, account, chunk)
if err != nil {
log.Error("can't fetch erc20 token balance", "account", account, "error", err)
return nil
}
for idx, token := range chunk {
if !res[idx].Success {
continue
}
balance := new(big.Int)
balance.SetBytes(res[idx].Data)
updateBalance(account, token, balance)
}
return nil
})
}
}
} else {
for tokenIdx := range tokens {
for accountIdx := range accounts {
// Below, we set account, token and client from idx on purpose to avoid override
account := accounts[accountIdx]
token := tokens[tokenIdx]
client := clients[clientIdx]
group.Add(func(parent context.Context) error {
ctx, cancel := context.WithTimeout(parent, requestTimeout)
defer cancel()
balance, err := tm.getBalance(ctx, client, account, token)
if err != nil {
log.Error("can't fetch erc20 token balance", "account", account, "token", token, "error", err)
return nil
}
updateBalance(account, token, balance)
return nil
})
}
}
} }
} }
select { select {
case <-group.WaitAsync(): case <-group.WaitAsync():