With web3.eth.getStorageAt
you can read the complete storage of any contract externally (off-chain). But I want to make it clear - it is not as simple, as the 1. variable at position 0 (the a
in your example), and the 6. variable at position 5 (the z
in your example).
getStorageAt
returns a storage slot, which is 32 bytes. And to read the variables data, you indeed have to know the order in which the variables are declared in a contract, but also you need to know the data types. As the variables of the fixed size could be stored in a single storage slot if they occupy <= 32 bytes.
For example, if your contract would be:
address private a;
bool private b;
Reading the first slot web3.eth.getStorageAt("0x501...", 0)
would return not just the address a
but also the value for b
.
The address
requires 20
bytes. The bool
: 1
byte. That is why the second variable is not stored at the second slot, but is placed into the first one, as the first slot has enough storage space left: 32bytes - sizeof(address) = 12bytes
.
This means, you need to know the slot number for a variable, but also the size and offset.
address private a; // slot 0, size 20, offset 0
bool private b; // slot 0, size 1, offset 20
Things get more complicated with arrays, mappings, string and dynamically sized types.
For example, for arrays:
- Fixed length: each item as a single variable:
// slot 0 for item0
// slot 1 for item1
// slot 2 for item2
address[3] private a;
// slot 3
address private b;
// slot 0 for current array length
// each item at specific index is accessed under the slot: keccak256(encodePacked(0)) + index
address[] private a;
// slot 1
address private b;
- Multiple slots per item in dynamic arrays
struct Data {
address user;
uint256 balance;
}
// slot 0 for current array length
// 2 slots per item
// a[0].user would be : keccak256(encodePacked(0)) + 0 * 2 + 0
// a[0].balance would be: keccak256(encodePacked(0)) + 0 * 2 + 1
// a[1].user would be : keccak256(encodePacked(0)) + 1 * 2 + 0
// a[1].balance would be: keccak256(encodePacked(0)) + 1 * 2 + 1
Data[] private a;
// slot 1
address private b;
Another caveat could be inheritance:
contract A {
// slot 0
address private a;
}
contract B is A {
// slot 1
address private b;
}
This could be indeed very complicated manually to count the positions and offsets, that's why I use 0xweb
auto-generated classes to access private storage of contracts, which are validated at Etherscan
and co. The tool generates TypeScript class to call functions (read
/write
) of the contract, but also it generates a storage reader class, so you can access all variables by name, for example - if your contract is validated at Etherscan, you could generate the TypeScript class as:
0xweb i 0x501... --name MyContract --chain eth
import { MyContract } from '@0xweb/eth/MyContract/MyContract'
let contract = new MyContract();
await contract.storage.a();
await contract.storage.z();
// or the first item of the array
await contract.storage.list(0);
// mapping
await contract.storage.people('0xFF01'/* bytes32 */ );