Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance regression when asked for multiple tokens balances #4865

Open
tommasini opened this issue Oct 21, 2024 · 1 comment
Open

Performance regression when asked for multiple tokens balances #4865

tommasini opened this issue Oct 21, 2024 · 1 comment
Assignees
Labels
discussion Questions, feedback and general information.

Comments

@tommasini
Copy link

Ethers Version

6.13.4

Search Terms

performance

Describe the Problem

Currently trying version ^6 to see if a performance issue was solved on version ^5 (ethersproject/contracts).

Using this abi single-call-balance-checker-abi, asking for multiple (1000 each time) tokens balances against the account address, created a big drop of JavaScript FPS on Android. When tried to do it with version ^6 the time changed from 9821 ms to 19725 ms.

The way it was tested:

    const contract = new Contract(
      contractAddress,
      abiSingleCallBalancesContract,
      provider,
    );
    const start = Date.now();
    const result = await contract.balances([selectedAddress], tokensToDetect);
    const end = Date.now();
    console.log('ENTER time spent on contract.balances:', end - start);

tokensToDetect = ["0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f", "0x1e1f509963a6d33e169d9497b11c7dbfe73b7f13",...]

I was able to trace down on version 5 a recursive function that it was using a lot of computational needs, that it was really noticeable with Android (Hermes enabled), making the app freeze/slow down.
I was not able to trace down what happened on version 6. Would love some thoughts from the maintainers! Also I'm here to help to trace down the issue.

If the maintainers have also an idea of how we could improve the performance on v^5 or create a fix for v^6 be more performative than v^5 I'm here to help as well!

Thanks for your time!

Code Snippet

const contract = new Contract(
      contractAddress,
      abiSingleCallBalancesContract,
      provider,
    );
    const start = Date.now();
    const result = await contract.balances([selectedAddress], tokensToDetect);
    const end = Date.now();
    console.log('ENTER time spent on contract.balances:', end - start);


### Contract ABI

```shell
module.exports = [
    {
     "payable": true,
     "stateMutability": "payable",
     "type": "fallback"
    },
    {
     "constant": true,
     "inputs": [
      {
       "name": "user",
       "type": "address"
      },
      {
       "name": "token",
       "type": "address"
      }
     ],
     "name": "tokenBalance",
     "outputs": [
      {
       "name": "",
       "type": "uint256"
      }
     ],
     "payable": false,
     "stateMutability": "view",
     "type": "function"
    },
    {
     "constant": true,
     "inputs": [
      {
       "name": "users",
       "type": "address[]"
      },
      {
       "name": "tokens",
       "type": "address[]"
      }
     ],
     "name": "balances",
     "outputs": [
      {
       "name": "",
       "type": "uint256[]"
      }
     ],
     "payable": false,
     "stateMutability": "view",
     "type": "function"
    }
   ]


### Errors

```shell
n/a

Environment

Ethereum (mainnet/ropsten/rinkeby/goerli), React Native/Expo/JavaScriptCore

Environment (Other)

Android with Hermes

@tommasini tommasini added investigate Under investigation and may be a bug. v6 Issues regarding v6 labels Oct 21, 2024
@ricmoo
Copy link
Member

ricmoo commented Dec 3, 2024

This should not have changed much between v5 and v6, but for your purposes, I believe you likely want to use the MulticallProvider, which will automatically batch all the calls into a single multi-call eth_call.

It should handle everything for you, as long as you preserve the async context, using something akin to:

const multicallProvider = new MulticallProvider(provider);
const balances = await Promise.all(tokens.map((tokenAddr) => {
    const contract = new Contract(tokenAddr, tokenAbi, multicallProvider);
    return contract.balanceOf(addr);
}));

Each Contract call will add to the list of calls, and the MulticallProvider will prepare a single call that can resolve many calls made locally from the JSON-RPC node.

@ricmoo ricmoo added discussion Questions, feedback and general information. and removed investigate Under investigation and may be a bug. v6 Issues regarding v6 labels Dec 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Questions, feedback and general information.
Projects
None yet
Development

No branches or pull requests

3 participants
@ricmoo @tommasini and others