Ethers.js: [question] How to convert address/bignumber to hex string?

Created on 3 Oct 2020  ·  6Comments  ·  Source: ethers-io/ethers.js

Hello, trying to implement the following tutorial in ethers https://medium.com/aigang-network/how-to-read-ethereum-contract-storage-44252c8af925 in order to load data from a "private" mapping.

With respect to the mapping example, how would the following code look in ethers?

index = '0000000000000000000000000000000000000000000000000000000000000005'
key =  '00000000000000000000000xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6'
let newKey =  web3.sha3(key + index, {"encoding":"hex"})
console.log(web3.eth.getStorageAt(contractAddress, newKey))
console.log('DEC: ' + web3.toDecimal(web3.eth.getStorageAt(contractAddress, newKey)))

I have tried the following which raises an error EXCEPTION Error: data out-of-bounds (length=32, offset=64, code=BUFFER_OVERRUN, version=abi/5.0.5):

let key = ethers.getAddress('0x...');
let offset = ethers.BigNumber.from(5);
let mappingKey = ethers.utils.keccak256([key, offset]);
let mappingData = await ethers.provider.getStorageAt(contractAddress, mappingKey, blockNumber);

Specifically, how does one convert the key and offset to appropriately sized hex strings for the hash function? Thanks in advance!

discussion

Most helpful comment

Heya!

You are on the right track. To accomplish the same thing in ethers, here is the demo re-written a bit, with more documentation added.

const { BigNumber, ethers, utils } = require("ethers");

(async function() {
    // Connect to Ropsten
    const provider = ethers.getDefaultProvider("ropsten");

    // The contract address
    const addr = "0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b";

    // The key into the mapping which is located at the given Solidity slot
    const key = "0xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6";
    const index = 5;

    // The pre-image used to compute the Storage location
    const newKeyPreimage = utils.concat([
        // Mappings' keys in Solidity must all be word-aligned (32 bytes)
        utils.hexZeroPad(key, 32),

        // Similarly with the slot-index into the Solidity variable layout
        utils.hexZeroPad(BigNumber.from(index).toHexString(), 32),
    ]);

    console.log("New Key Preimage:", utils.hexlify(newKeyPreimage));
    // "0x000000000000000000000000bccc714d56bc0da0fd33d96d2a87b680dd6d0df60000000000000000000000000000000000000000000000000000000000000005"

    const newKey = utils.keccak256(newKeyPreimage);
    console.log("New Key:", newKey);
    // "0xafef6be2b419f4d69d56fe34788202bf06650015554457a2470181981bcce7ef"

    const value = await provider.getStorageAt(addr, newKey);
    console.log("Value:", value);
    // "0x0000000000000000000000000000000000000000000000000000000000000058"
})();

I hope that helps! Let me know if you have any other questions. :)

All 6 comments

Heya!

You are on the right track. To accomplish the same thing in ethers, here is the demo re-written a bit, with more documentation added.

const { BigNumber, ethers, utils } = require("ethers");

(async function() {
    // Connect to Ropsten
    const provider = ethers.getDefaultProvider("ropsten");

    // The contract address
    const addr = "0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b";

    // The key into the mapping which is located at the given Solidity slot
    const key = "0xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6";
    const index = 5;

    // The pre-image used to compute the Storage location
    const newKeyPreimage = utils.concat([
        // Mappings' keys in Solidity must all be word-aligned (32 bytes)
        utils.hexZeroPad(key, 32),

        // Similarly with the slot-index into the Solidity variable layout
        utils.hexZeroPad(BigNumber.from(index).toHexString(), 32),
    ]);

    console.log("New Key Preimage:", utils.hexlify(newKeyPreimage));
    // "0x000000000000000000000000bccc714d56bc0da0fd33d96d2a87b680dd6d0df60000000000000000000000000000000000000000000000000000000000000005"

    const newKey = utils.keccak256(newKeyPreimage);
    console.log("New Key:", newKey);
    // "0xafef6be2b419f4d69d56fe34788202bf06650015554457a2470181981bcce7ef"

    const value = await provider.getStorageAt(addr, newKey);
    console.log("Value:", value);
    // "0x0000000000000000000000000000000000000000000000000000000000000058"
})();

I hope that helps! Let me know if you have any other questions. :)

Thanks for the help! I am still getting the same error with the updated code:

let hexKey = ethers.utils.hexZeroPad(key, 32);
let hexIndex = ethers.utils.hexZeroPad(ethers.BigNumber.from(index).toHexString(), 32);
let mappingKeyPreimage = ethers.utils.concat([hexKey, hexIndex]);
let mappingKey = ethers.utils.keccak256(mappingKeyPreimage);

The error is

[Sat Oct 03 2020 16:42:21] [ERROR]  Error: data out-of-bounds (length=32, offset=64, code=BUFFER_OVERRUN, version=abi/5.0.5)
    at Logger.makeError (/app/node_modules/@ethersproject/logger/src.ts/index.ts:205:28)
    at Logger.throwError (/app/node_modules/@ethersproject/logger/src.ts/index.ts:217:20)
    at Reader._peekBytes (/app/node_modules/@ethersproject/abi/src.ts/coders/abstract-coder.ts:171:24)
    at Reader.readBytes (/app/node_modules/@ethersproject/abi/src.ts/coders/abstract-coder.ts:185:26)
    at Reader.readValue (/app/node_modules/@ethersproject/abi/src.ts/coders/abstract-coder.ts:192:36)
    at NumberCoder.decode (/app/node_modules/@ethersproject/abi/src.ts/coders/number.ts:44:28)
    at /app/node_modules/@ethersproject/abi/src.ts/coders/array.ts:108:31
    at Array.forEach (<anonymous>)
    at Object.unpack (/app/node_modules/@ethersproject/abi/src.ts/coders/array.ts:89:12)
    at TupleCoder.decode (/app/node_modules/@ethersproject/abi/src.ts/coders/tuple.ts:27:41) {
  reason: 'data out-of-bounds',
  code: 'BUFFER_OVERRUN',
  length: 32,
  offset: 64
}

I don't understand what the error is indicating. It looks like there is a fixed sized buffer somewhere. Is this because of my inputs? key is an address from ethers.utils.getAddress(...) and index is just an int.

Also, utils.hexConcat does not appear to be a function as the docs suggest https://docs.ethers.io/v5/api/utils/bytes/#utils-hexConcat, I've tried it as a substitute to utils.concat.

Ah! Your solution worked perfectly, I was dealing with parsing the return of getStorageAt but was confused because the stack trace did not show which function I had called. Obviously this is because its an async function, but I hadnt realized. Thanks!

Glad to hear it! :)

(I'll also be sure to export hexConcat in the next release too)

Was this page helpful?
0 / 5 - 0 ratings