10

I read the documentation and tried some other approaches but it's not working for me. I'm not sure what I'm missing.

Let's say I have a contract with a mapping and I would like to know the storage slot location for a particular key and be able to access it directly from something like web3.eth.getStorageAt(contractAddress, mappingKeyIndexInStorage):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

contract MappingStorage {

mapping(address => uint) public balances;

constructor() {
    balances[0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405] = 678;
    balances[0x66B0b1d2930059407DcC30F1A2305435fc37315E] = 501; 

}

function getStorageLocationForKey(address _key, uint256 p) public pure returns(bytes32) {
    // There is no straight foward way in solidity to concatenate 2 values, so I'm trying encoding here.
    bytes memory encoded = abi.encode(_key, p);
    return keccak256(encoded);
}

}

The getStorageLocationForKey function is just an utility that I would like to use to derive this storage slot index from using Solidity.

Since the key is an address, I left padded the address to have a 32 bytes value:

0x000000000000000000000006827b8f6cc60497d9bf5210d602C0EcaFDF7C4050

Since the mapping, in this case, is declared at storage slot position 0, I padded it to 32 bytes:

0x0000000000000000000000000000000000000000000000000000000000000000

Concatenating them without the 0x prefix:

0000000000000000000000006827b8f6cc60497d9bf5210d602C0EcaFDF7C4050000000000000000000000000000000000000000000000000000000000000000

And its keccak256 hash:

6ccfcacc19c8d102cc5880b825323e63defac7b2ba969c7dc33950b298248835

You can try the keccak256 here: https://emn178.github.io/online-tools/keccak_256.html

I try:

web3.eth.getStorageAt(contractAddress, "0x6ccfcacc19c8d102cc5880b825323e63defac7b2ba969c7dc33950b298248835")

And it returns 0x bytes. I'm expecting to get the 678 value which is what it's saved at that address key.

I'm I missing something?

I would like to see a solution implemented in Solidity.

Here is an instance of this contract deployed in Rinkeby testnet:

0xC216FdC8fb87A151d69313e18a80a58bebBA7267

Jeremy Then
  • 4,599
  • 3
  • 5
  • 28

2 Answers2

13

I managed to solve it myself:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

contract MappingStorage {

mapping(address => uint) public balances;
uint8 constant balancesMappingIndex = 0;

constructor() {
    balances[0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405] = 678;
    balances[0x66B0b1d2930059407DcC30F1A2305435fc37315E] = 501;
}

function getStorageLocationForKey(address _key) public pure returns(bytes32) {
    // This works pretty well as concatenation. For the address 0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405, 
    // the storage slot index hash would be: 0x86dfc0930cb222883cc0138873d68c1c9864fc2fe59d208c17f3484f489bef04
    return keccak256(abi.encode(_key, balancesMappingIndex));
}

function getKeyEncodedWithMappingIndex(address _key) public pure returns(bytes memory) {
    // For the address 0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405, the encoded data would be:
    // 0x0000000000000000000000006827b8f6cc60497d9bf5210d602c0ecafdf7c4050000000000000000000000000000000000000000000000000000000000000000
  return abi.encode(_key, balancesMappingIndex);
}

}

I was doing a couple of things wrong. First, using the emn178.github.io site to calculate the keccak256 hash of the concatenation of the encoded and padded address and mapping storage index, I had the address checksummed, so, there were some uppercase characters and thus it was yielding the wrong hash. Also, the 'Input type' on that site was 'Text' by default, so I put it as 'Hex' so it knows how to treat my input while calculating the keccak256 hash.

I managed to implement it with Solidity, as I needed and it's working alright now.

Now calling the contract with the hash returns what I was expecting:

web3.eth.getStorageAt("0xC216FdC8fb87A151d69313e18a80a58bebBA7267", "0x86dfc0930cb222883cc0138873d68c1c9864fc2fe59d208c17f3484f489bef04")

Returns:

0x00000000000000000000000000000000000000000000000000000000000002a6

Which parsed to decimal is:

678

Jeremy Then
  • 4,599
  • 3
  • 5
  • 28
5

here you have an example using web3.js and soliditySha3

soliditySha3:

Will calculate the sha3 of given input parameters in the same way solidity would. This means arguments will be ABI converted and tightly packed before being hashed.

async function read() {
  const address= "0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405";
  const slot = 0;
  const paddedAddress = web3.utils.leftPad(address, 64);
  const paddedSlot = web3.utils.padLeft(slot, 64);
  const hash = web3.utils.soliditySha3(paddedAddress, paddedSlot);
  const result = await web3.eth.getStorageAt(
    "0xC216FdC8fb87A151d69313e18a80a58bebBA7267",
    hash
  );
  console.log("result:", parseInt(result, 16));
}

and here you have an example with ethers.js

async function read() {
  const address = "0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405";
  const slot = 0;
  const paddedAddress = ethers.utils.hexZeroPad(address, 32);
  const paddedSlot = ethers.utils.hexZeroPad(slot, 32);
  const concatenated = ethers.utils.concat([paddedAddress, paddedSlot]);
  const hash = ethers.utils.keccak256(concatenated);
  const result = await provider.getStorageAt(
    "0xC216FdC8fb87A151d69313e18a80a58bebBA7267",
    hash
  );
  console.log("result ethers:", parseInt(result, 16));
}
Javier Marchetti
  • 1,217
  • 3
  • 12
  • Thank you Javier, that looks good. But I was looking for a way to implement that logic in Solidity also. But thanks for your effort. I managed to solve my issue. – Jeremy Then Aug 12 '22 at 03:15