Appearance
Read a storage slot
Read stETH's raw storage — the slots where an Aragon app keeps its kernel and its id, and a counter you can check against a call.
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH as s
| project {
kernel: s.$storage[keccak256('aragonOS.appStorage.kernel')] as address,
appId: s.$storage[keccak256('aragonOS.appStorage.appId')],
elRewards: s.$storage[keccak256('lido.Lido.totalELRewardsCollected')] as uint256,
elRewardsFn: s.getTotalELRewardsCollected()
}| kernel | appId | elRewards | elRewardsFn |
|---|---|---|---|
| ethereum:0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc | 0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320 | 238142947935636941239384 | 238142947935636941239384 |
kernel is the Aragon kernel that governs the app, appId the 32-byte id it is registered under, and elRewards the execution-layer rewards collected so far, in wei — the same number getTotalELRewardsCollected() returns, so the raw read and the call agree.
How it reads
s.$storage[slot] reads one word of the contract's storage at the row's block, with no ABI involved. The slot is a number or a 32-byte hex, and the answer is bytes32. Aragon and Lido keep these values at slots named in their source — keccak256('aragonOS.appStorage.kernel') — so keccak256 of the same string is the slot. as address reinterprets the word the way Solidity stores an address — the low 20 bytes — and as uint256, as bool, as bytes4 do the same for their types; appId stays as it is because it is a bytes32.
The cross-check is why you read storage at all. A call goes through the ABI and the proxy; a slot goes through neither. When the two agree you know which slot holds what, and on a contract with no verified ABI the slot is still there to read.
Mappings and arrays are not at fixed slots; slot() computes where they are:
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
let wstETH = ethereum:0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
from stETH as s
| project { raw: s.$storage[slot(mapping: 0, key: wstETH)] as uint256, shares: s.sharesOf(wstETH) }slot(mapping: 0, key: wstETH) is the slot of m[wstETH] for a mapping m declared in slot 0; put it beside sharesOf(wstETH) and the two columns tell you whether stETH's share ledger is that mapping. slot(array: n, index: i) and slot(base: s, offset: k) cover dynamic arrays and struct members.