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!