1

I would like to access the storage of a specific contract using ethers.js and getStorageAt, but I can't get my head around how to do this for the mapping(address => mapping(address => address)) type. I am experimenting with the UniswapV2Factory.sol contract of Uniswap available here (https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Factory.sol).

Here are the lines that matter:

contract UniswapV2Factory is IUniswapV2Factory {
    address public feeTo;
    address public feeToSetter;

    mapping(address => mapping(address => address)) public getPair;
    // ...
}

Based on the getPair type definition, how could I iterate through the values stored, and find existing pairs? Here's some sample JavaScript code - I would like to iterate through getPair in a similar fashion:

const contract = await new ethers.getContractAt("UniswapV2Factory", "0x...");

const token0 = "0x...";

for (const token1 of Object.keys(contract.getPair[token0])) {
  console.log("token0", token0);
  console.log("token1", token1);
  console.log("pair", contract.getPair[token0][token1]);
}

Thanks

2 Answers 2

4

To do it that way, you must know what to look for, meaning that you should know beforehand some combinations of tokens addresses to compute the pair address for. Or, as @longhuynh suggested, you could rely on a service that does this dynamically for you.

First, you should read the mappings storage layout definition, especially the part / example on nested mappings.

The value corresponding to a mapping key k is located at keccak256(h(k) . p) where . is concatenation and h is a function that is applied to the key depending on its type.

In your example, p is 2 (encoded on 32 bytes) because getPair is declared at storage slot 2. To get the first level of the mapping you need to compute :

keccak256(abi.encode(token0, uint256(2))

Now to get to the second level, you need to reapply the same construction by considering the previous output as the storage slot of the inner mapping:

keccak256(abi.encode(token1) . uint256(keccak256(abi.encode(token0, uint256(2))))

Turning this into ethers.js :

const ethers = require("ethers");

const ENDPOINT = "THE-ENDPOINT-URL";
const CONTRACT_ADDRESS = "THE-CONTRACT-ADDRESS";
const TOKEN0 = "THE-ADDRESS-OF-TOKEN0";
const TOKEN1 = "THE-ADDRESS-OF-TOKEN1";
const MAPPING_SLOT = ethers.BigNumber.from(2);

async function getPair() {
  const provider = await new ethers.providers.JsonRpcProvider(ENDPOINT);
  const abiCoder = new ethers.utils.AbiCoder();

  // ABI Encode the first level of the mapping
  // abi.encode(address(TOKEN0), uint256(MAPPING_SLOT))
  // The keccak256 of this value will be the "slot" of the inner mapping
  const firstLevelEncoded = abiCoder.encode(
    ["address", "uint256"],
    [TOKEN0, MAPPING_SLOT]
  );

  // ABI Encode the second level of the mapping
  // abi.encode(address(TOKEN1))
  const secondLevelEncoded = abiCoder.encode(["address"], [TOKEN1]);

  // Compute the storage slot of getPair[token0][token1]
  // keccak256(abi.encode(address(TOKEN1)) . abi.encode(address(TOKEN0), uint256(MAPPING_SLOT)))
  const slot = ethers.utils.keccak256(
    ethers.utils.concat([
      secondLevelEncoded,
      ethers.utils.keccak256(firstLevelEncoded),
    ])
  );

  console.log(await provider.getStorageAt(CONTRACT_ADDRESS, slot));
}

getPair();

Feel free to modify the logic a little, so that you can pass the relevant values as parameters and simply iterate over the known token addresses that you want to check the pair for.

1

I don't think it is possible to iterate through the value of a mapping, unless you store an array of keys for that mapping. Smart contract's storage layout for dynamic mapping is quite complicated (you can take a look at the solidity's document for more details).

For a solution, perhaps you should consider using The Graph? It basically "listens" to the smartcontract's events, and store it in a structured database, so you can fetch that data and iterate easily.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.