Destiner's Notes

Multicall2: Dealing with Calls that Can Fail

18 April 2022

So far, we’ve been looking at the Multicall contract to learn how to query multiple contracts in a single call.

The biggest problem with the original contract is that if one of the underlying calls fails, the whole query would fail. For example, if you query the balance of the 100 tokens in a single request, and one of those tokens is broken and throws on balanceOf call, the entire request will fail.

To solve that, MakerDAO, creator of the original Multicall contract, released Multicall2. New contract has two additional functions: tryAggregate and tryBlockAndAggregate. The only difference between them is the second function returns the block at which the call was made.

Here’s the source code of the tryAggregate method:

function tryAggregate(bool requireSuccess, Call[] memory calls) public returns (Result[] memory returnData) {
    returnData = new Result[](calls.length);
    for(uint256 i = 0; i < calls.length; i++) {
        (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
        if (requireSuccess) {
            require(success, "Multicall2 aggregate: call failed");
        }
        returnData[i] = Result(success, ret);
    }
}

As you can see, this function is similar to the aggregate function, with the only distinction being that the require(success) is optional now.

Example

Frankly, using Multicall2 in practice is almost the same as using the original contract. The only difference is specifying requireSuccess in the batch call:

import { providers, Contract } from 'ethers';

import * as multicall2Abi from './abi/multicall2.json';

const key = 'YOUR_KEY_HERE';
const provider = new providers.AlchemyProvider(1, key);
const multicall2Address = '0x5ba1e12693dc8f9c48aad8770482f4739beed696';

const multicall = new Contract(multicall2Address, multicall2Abi, provider);

async function fetch() {
  const daiAddress = '0x6b175474e89094c44da98b954eedeac495271d0f';
  const balanceOfSelector = `70a08231`; // keccak256('balanceOf')
  const balanceOfParams = `0000000000000000000000002a1530c4c41db0b0b2bb646cb5eb1a67b7158667`; // Uniswap pool address padded to 64 characters

  const requireSuccess = false;
  const calls = [
    {
      target: daiAddress,
      callData: `0x${balanceOfSelector}${balanceOfParams}`,
    },
  ];
  const result = await multicall.aggregate(requireSuccess, calls);
  const data = result[1];
}

fetch();

Note: if one of the underlying calls will fail, it will return null.

Bonus: Multicall3

There is another extension of the original contract made by Matt Solomon, which allows you to control which calls in the batch can fail and which are not. In Multicall3, you provide an array of calls and an array of booleans for each call.

References

You can see the source code of the Multicall2 contract on GitHub.

You can also see the deployed contract on Ethereum, Polygon, and BSC.

To abstract the complexity of dealing with ABI encoding, you can use ethcall.