Skip to main content

WTF Ethers: 22. Read Any Data

I've been revisiting ethers.js recently to refresh my understanding of the details and to write a simple tutorial called "WTF Ethers" for beginners.

Twitter: @0xAA_Science

Community: Website wtf.academy | WTF Solidity | discord | WeChat Group Application

All the code and tutorials are open-sourced on GitHub: github.com/WTFAcademy/WTF-Ethers


All data on Ethereum is public, so private variables are not actually private. In this lesson, we will discuss how to read arbitrary data from a smart contract.

Smart Contract Storage Layout

The storage in Ethereum smart contracts is a mapping of uint256 -> uint256. The size of uint256 is 32 bytes, and this fixed-sized storage space is called a slot. The contract's data is stored in individual slots, starting from slot 0 by default and continuing sequentially. Each primitive data type occupies one slot, such as uint, address, and so on. However, more complex structures like arrays and mappings are more complicated, as detailed in the documentation.

Therefore, even for private variables without a getter function, you can still read their values by accessing the respective slot.

getStorageAt

ethersjs provides the getStorageAt() function for developers to conveniently read the value of a specific slot:

const value = await provider.getStorageAt(contractAddress, slot)

getStorageAt() takes two arguments: the contract address contractAddress, and the index of the variable's slot that you want to read.

Reading Arbitrary Data Script

Now, let's write a script that utilizes the getStorageAt() function to read the owner of the Arbitrum cross-chain bridge contract. This bridge contract is an upgradable proxy contract, and the owner is stored in a specific slot to avoid variable collisions, without a dedicated function for reading it. We can use getStorageAt() to read it.

Contract Address: 0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a
Slot Index: 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103

Code:

import { ethers } from "ethers";

// Prepare Alchemy API (see https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md)
const ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN';
const provider = new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);

// Target Contract Address: Arbitrum ERC20 bridge (Mainnet)
const addressBridge = '0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a' // DAI Contract
// Contract Owner Slot
const slot = `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`

const main = async () => {
console.log("Reading data from a specific slot...")
const privateData = await provider.getStorage(addressBridge, slot)
console.log("Data read (owner address): ", ethers.getAddress(ethers.dataSlice(privateData, 12)))
}

main()

Output:

Summary

In this lesson, we have learned how to read arbitrary data from a smart contract, including private data. Due to the transparency of the Ethereum network, it is essential not to store sensitive information in smart contracts!