0

I am using the following code to successfully find a balance storage slot of Tokens written in Solidity:

  async findStorageSlot(tokenAddr: EthAddress): Promise<number | void> {
    const token = new Contract(tokenAddr.toString(), ERC20.abi, this.ethersProvider);
    const randomAddress = '0x8b359fb7a31620691dc153cddd9d463259bcf29b';

    const probeValue = BigNumber.from(356);
    const encodedBalance = utils.defaultAbiCoder.encode(['uint'], [probeValue]);

    for (let i = 0; i < 100; i++) {
      const userBalanceSlot = utils.keccak256(utils.defaultAbiCoder.encode(['address', 'uint'], [randomAddress, i]));

      await this.ethereumRpc.setStorageAt(tokenAddr, userBalanceSlot, encodedBalance);
      const balance: BigNumber = await token.balanceOf(randomAddress);
      if (balance.eq(probeValue)) {
        return i;
      }
    }
    return;
  }

  public async setStorageAt(addr: EthAddress, slot: string, value: string): Promise<any> {
    const result = await this.provider.request({
      method: 'hardhat_setStorageAt',
      params: [addr.toString(), slot, value],
    });
    return result;
  }

But when I try to use this method on a Vyper token the method fails. Is it the case that Vyper's HashMap uses different value slot encoding than Solidity's mapping?

Thank you

1 Answer 1

1

The key in mappings is computed differently. In solidity the hash is computed on packed variables and the order is user address first and then balances mapping slot. In vyper it's the other way round. Here is a full code of finding the slot and then setting balance:

interface SlotInfo {
  slot: bigint;
  isVyper: boolean;
}

  async setBalance(tokenAddr: EthAddress, userAddr: EthAddress, balance: bigint) {
    const slotInfo = await this.findBalancesSlot(tokenAddr);

    const userBalanceSlot = slotInfo.isVyper
      ? utils.hexStripZeros(
          utils.keccak256(utils.defaultAbiCoder.encode(['uint', 'address'], [slotInfo.slot, userAddr.toString()])),
        )
      : utils.hexStripZeros(
          utils.keccak256(utils.defaultAbiCoder.encode(['address', 'uint'], [userAddr.toString(), slotInfo.slot])),
        );

    const encodedBalance = utils.defaultAbiCoder.encode(['uint'], [balance]);

    const result = await this.ethereumRpc.setStorageAt(tokenAddr, userBalanceSlot, encodedBalance);
    if (!result) {
      throw new Error('Setting token balance failed');
    }
  }

  private async findBalancesSlot(tokenAddr: EthAddress): Promise<SlotInfo> {
    const token = new Contract(tokenAddr.toString(), ERC20.abi, this.ethersProvider);
    const randomAddress = '0x8b359fb7a31620691dc153cddd9d463259bcf29b';

    const probeValue = BigNumber.from(356);
    const encodedBalance = utils.defaultAbiCoder.encode(['uint'], [probeValue]);

    for (let i = 0n; i < 100; i++) {
      const userBalanceSlot = utils.hexStripZeros(
        utils.keccak256(utils.defaultAbiCoder.encode(['address', 'uint'], [randomAddress, i])),
      );
      await this.ethereumRpc.setStorageAt(tokenAddr, userBalanceSlot, encodedBalance);
      const balance: BigNumber = await token.balanceOf(randomAddress);
      if (balance.eq(probeValue)) {
        return { slot: i, isVyper: false };
      }
    }

    for (let i = 0n; i < 100; i++) {
      const userBalanceSlot = utils.hexStripZeros(
        utils.keccak256(utils.defaultAbiCoder.encode(['uint', 'address'], [i, randomAddress])),
      );
      await this.ethereumRpc.setStorageAt(tokenAddr, userBalanceSlot, encodedBalance);
      const balance: BigNumber = await token.balanceOf(randomAddress);
      if (balance.eq(probeValue)) {
        return { slot: i, isVyper: true };
      }
    }
    throw new Error('Balances slot not found');
  }

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.