ethers.js: TypeError: Cannot assign to read only property '0' of object '[object Array]'

Ethers Version

6.3.0

Search Terms

assign, read only, v6

Describe the Problem

I’m working on some examples for Multicall3 with ethers v6, and this line throws with the below error:

TypeError: Cannot assign to read only property '0' of object '[object Array]'
    at /Users/mds/Documents/projects/multicall/examples/typescript/node_modules/ethers/src.ts/abi/fragments.ts:723:34
    at ParamType.#walkAsync (/Users/mds/Documents/projects/multicall/examples/typescript/node_modules/ethers/src.ts/abi/fragments.ts:761:13)
    at /Users/mds/Documents/projects/multicall/examples/typescript/node_modules/ethers/src.ts/abi/fragments.ts:722:37
    at Result.forEach (<anonymous>)
    at Proxy.<anonymous> (/Users/mds/Documents/projects/multicall/examples/typescript/node_modules/ethers/src.ts/abi/coders/abstract-coder.ts:118:42)
    at ParamType.#walkAsync (/Users/mds/Documents/projects/multicall/examples/typescript/node_modules/ethers/src.ts/abi/fragments.ts:721:20)
    at ParamType.walkAsync (/Users/mds/Documents/projects/multicall/examples/typescript/node_modules/ethers/src.ts/abi/fragments.ts:783:24)
    at /Users/mds/Documents/projects/multicall/examples/typescript/node_modules/ethers/src.ts/contract/contract.ts:157:22
    at Array.map (<anonymous>)
    at resolveArgs (/Users/mds/Documents/projects/multicall/examples/typescript/node_modules/ethers/src.ts/contract/contract.ts:156:37)

I’ve used pretty much this exact code snippet in v5 without issue, but have not been able to figure out why this throws. The input data to that call seems correct, and if I change the input data to again use the resolverCalls inputs, it succeeds (i.e. the inputs seem to be the cause to the issue). I feel like I am probably missing something obvious here, but can’t figure it out 😅

Code Snippet

import { Contract, Interface, JsonRpcProvider, namehash } from 'ethers';

export const MULTICALL_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
export const MULTICALL_ABI = [
  // https://github.com/mds1/multicall
  'function aggregate(tuple(address target, bytes callData)[] calls) payable returns (uint256 blockNumber, bytes[] returnData)',
  'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)',
  'function aggregate3Value(tuple(address target, bool allowFailure, uint256 value, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)',
  'function blockAndAggregate(tuple(address target, bytes callData)[] calls) payable returns (uint256 blockNumber, bytes32 blockHash, tuple(bool success, bytes returnData)[] returnData)',
  'function getBasefee() view returns (uint256 basefee)',
  'function getBlockHash(uint256 blockNumber) view returns (bytes32 blockHash)',
  'function getBlockNumber() view returns (uint256 blockNumber)',
  'function getChainId() view returns (uint256 chainid)',
  'function getCurrentBlockCoinbase() view returns (address coinbase)',
  'function getCurrentBlockDifficulty() view returns (uint256 difficulty)',
  'function getCurrentBlockGasLimit() view returns (uint256 gaslimit)',
  'function getCurrentBlockTimestamp() view returns (uint256 timestamp)',
  'function getEthBalance(address addr) view returns (uint256 balance)',
  'function getLastBlockHash() view returns (bytes32 blockHash)',
  'function tryAggregate(bool requireSuccess, tuple(address target, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)',
  'function tryBlockAndAggregate(bool requireSuccess, tuple(address target, bytes callData)[] calls) payable returns (uint256 blockNumber, bytes32 blockHash, tuple(bool success, bytes returnData)[] returnData)',
];

export const ERC20_ABI = [
  'event Approval(address indexed owner, address indexed spender, uint256 value)',
  'event UnBlacklisted(address indexed _account)',
  'function allowance(address owner, address spender) view returns (uint256)',
  'function approve(address spender, uint256 value) returns (bool)',
  'function balanceOf(address account) view returns (uint256)',
  'function decimals() view returns (uint8)',
  'function name() view returns (string)',
  'function symbol() view returns (string)',
  'function totalSupply() view returns (uint256)',
  'function transfer(address to, uint256 value) returns (bool)',
  'function transferFrom(address from, address to, uint256 value) returns (bool)',
];

// Setup the provider.
const MAINNET_RPC_URL = process.env.MAINNET_RPC_URL;
if (!MAINNET_RPC_URL) throw new Error('Please set the MAINNET_RPC_URL environment variable.');
const provider = new JsonRpcProvider(MAINNET_RPC_URL);

// Get Multicall contract instance.
const multicall = new Contract(MULTICALL_ADDRESS, MULTICALL_ABI, provider);

// Reverse resolve ENS names for a list of addresses using the `aggregate3` method to support
// reverting calls. The process shown here for reverse resolving ENS names is based on:
//   https://github.com/ethers-io/ethers.js/blob/0802b70a724321f56d4c170e4c8a46b7804dfb48/src.ts/providers/abstract-provider.ts#L976
async function example2() {
  // Define some data.
  const users = [
    '0x8700B87C2A053BDE8Cdc84d5078B4AE47c127FeB',
    '0x9EAB9D856a3a667dc4CD10001D59c679C64756E7',
    '0x78d32460D0a53Ac2678e869Eb6b4f6bA9d2Ef360',
    '0x3B60e31CFC48a9074CD5bEbb26C9EAa77650a43F',
    '0x99FBa19112f221D0B44c9c22241f5e6b2Db715F6',
    '0xE943CA883ef3294E0FC55a1A14591aBeAD1B5927',
    '0x26E3a9c84fdB9b7fE33Dfd5E8D273D016e4e4Fb6',
  ];

  // Setup the contract addresses and interface methods that we'll need.
  const ensRegistryAddr = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e';
  const ensRegistryInterface = new Interface(['function resolver(bytes32) view returns (address)']);
  const resolverInterface = new Interface(['function name(bytes32) view returns (string)']);

  // CALL 1: Get the reverse resolver address for each account.
  // For each address, compute it's reverse resolver namehash.
  const nodes = users.map((addr) => namehash(addr.substring(2).toLowerCase() + '.addr.reverse'));

  // Prepare the calls to look up each user's resolver address.
  const resolverCalls = nodes.map((node) => ({
    target: ensRegistryAddr,
    allowFailure: true, // We allow failure for all calls.
    callData: ensRegistryInterface.encodeFunctionData('resolver', [node]),
  }));

  // Execute those calls.
  type Aggregate3Response = { success: boolean; returnData: string };
  const resolverResults: Aggregate3Response[] = await multicall.aggregate3.staticCall(
    resolverCalls
  );

  // Decode the responses.
  const resolverAddrs = resolverResults.map(({ success, returnData }, i) => {
    if (!success) throw new Error(`Failed to get resolver for ${users[i]}`);
    return ensRegistryInterface.decodeFunctionResult('resolver', returnData)[0];
  });

  // CALL 2: Get the name for each account.
  // First we prepare the calls.
  console.log('resolverAddrs:', resolverAddrs);
  const nameCalls = resolverAddrs.map((resolverAddr, i) => ({
    target: resolverAddr,
    allowFailure: true, // We allow failure for all calls.
    callData: resolverInterface.encodeFunctionData('name', [nodes[i]]),
  }));

  // Execute those calls.
  console.log('nameCalls:', nameCalls);
  const nameResults: Aggregate3Response[] = await multicall.aggregate3.staticCall(nameCalls);

  // // Decode the responses.
  // const names = nameResults.map(({ success, returnData }, i) => {
  //   if (!success) throw new Error(`Failed to get name for ${users[i]}`);
  //   if (returnData === '0x') return users[i]; // If no ENS name, return the address.
  //   return <string>resolverInterface.decodeFunctionResult('name', returnData)[0];
  // });

  // console.log('names:', names);
}

example2().catch(console.error);

Contract ABI

No response

Errors

No response

Environment

node.js (v12 or newer)

Environment (Other)

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 16 (4 by maintainers)

Most upvoted comments

A Result object is intended to be immutable. But it is a sub-class of Array, so any array copying method should work; like Array.from(result).

I’m considering changing this behaviour in v7, and would love feedback. 😃