aka the Ethereum Storage Time Machine 🧙♂️
EVM Storage Proofs allow to trustlessly prove a past storage value in a contract to other contracts.
@aragon/evm-storage-proofs: On-chain component for verifying storage proofs and tests.
@aragon/web3-proofs: Off-chain JS library for generating and locally verifying proofs.
- Fetching a value at a past block number (i.e.
ANT balance of 0xcafe1a77e... at block 5,000,000)
- Inspecting storage values that are not exposed through a contract's public API.
- Executing code in an EVM (or a different VM) on the EVM with real storage values.
- More fun things
- Geth >= 1.8.18 (you can start geth on a Docker container by running
npm run start:gethin the
- Parity very soon. Pull request was merged but it is still unreleased.
git clone https://github.com/aragon/evm-storage-proofs.git npm install cd packages/evm npm test
The best way to learn how the library and the contracts are used is by checking the test suite for
The current implementation uses the
eth_getProof RPC method (EIP 1186) to generate proofs.
For generating proofs using the JS library:
const Web3Proofs = require('@aragon/web3-proofs') // Not published to NPM yet, requires a local 'npm link' const web3proofs = new Web3Proofs(web3.currentProvider) // Or a web3.js 1.0 compatible provider const proof = await web3proofs.getProof(contractAddress, [slot1, slot2], blockNumber)
If running on a live network, proof generation requires an archive node (unless the proof is being generated for the
latest block). Whether proofs can be generated using the Ethereum light client protocol (LES
GetProofs) is currently being researched.
On-chain proof verification
StorageOracle contract can be used to verify a storage proof. There are two phases for proof verification:
1. Account proof verification
A block header contains the merkle root for the Ethereum state trie for that block. An account proof is a merkle proof of the state of an account, which verifies the fields of an account (
[nonce, balance, storageHash, codeHash]) for that block height.
In order to perform this verification for an account, the
StorageOracle must be provided with the block header blob, the block number and the merkle proof for the account.
StorageOracle will verify that the hash of provided
blockHeaderRLP is the valid
blockhash of the block number. The
stateRoot is then extracted from the block header and then the
accountStateProof is verified using the
stateRoot as its root.
If the proof is successfully verified, the
storageHash for the account at that
blockNumber is cached in the
storageHash is the root of the storage merkle trie for the account.
storageOracle.processStorageRoot(account, blockNumber, blockHeaderRLP, accountStateProof)
2. Storage proofs
A storage proof is a proof for a storage slot's value in an account at a certain block height. Contracts can store data in any of the
2^256 storage slots available, but normally they are ordered (see Solidity reference or Jorge Izquierdo's Devcon3 talk)
After having the
storageHash verified and cached in the
StorageOracle, merkle proofs in the account's storage trie can be verified. The
storageOracle#getStorage function will verify the merkle proof and return the storage value. In case of an exclusion proof (proving that no value is stored in that slot), the function will return
uint256 value = storageOracle.getStorage(account, blockNumber, slot, storageProof)
Each storage slot contains a 32 byte value (
bytes32), for more complex data structures multiple storage proofs can be done and the data structure can be composed.
Adapters: snapshotted token balances
StorageOracle contract just implements the logic for trustlessly getting a past storage value on other contracts, but in order to get more interesting data, adapters can be built that use the
StorageOracle for proving storage and give it extra meaning.
TokenStorageProofs contract exposes two functions
getTotalSupply, which provided with the correct proof, will return the historic values from the token contract storage.
🚨 Everything in this repo is highly experimental software.
It is not secure to use any of this code in production (mainnet) until proper security audits have been conducted.
📉 Test coverage is low
There are a lot of edge cases that haven't been properly tested yet. Test contributions would be highly appreciated!
🗃 Archive node required
Generating proofs for past block heights requires having access to an archive node.
Credits and resources
- Lorenz Breidenbach: the Merkle proof verification code is heavily inspired from Proveth's implementation.
- Jordi Baylina: JS implementation of merkle patricia trie proof verification
- Ethereum wiki: Patricia Tree spec
- Ethan Buchman: Understanding the Ethereum trie
- This amazing graph on how everything in Ethereum works: