Skip to main content

StorageProof

EIP-1186 storage slot proof for trustless contract storage verification.

Overview

StorageProof represents a proof for a single storage slot in a contract’s storage trie. It enables trustless verification of contract storage values without executing transactions or trusting external providers.

Type Definition

type StorageProofType = {
  /** The storage slot being proven */
  readonly key: StorageKeyType;

  /** The value stored at this slot (0 if uninitialized) */
  readonly value: StorageValueType;

  /** RLP-encoded Merkle Patricia Trie nodes (root to leaf) */
  readonly proof: readonly Uint8Array[];
};

type StorageProofLike = StorageProofType | {
  key: StorageKeyType;
  value: StorageValueType;
  proof: readonly Uint8Array[];
};

Usage

Create StorageProof

import * as StorageProof from './primitives/StorageProof/index.js';

const proof = StorageProof.from({
  key: 0n,  // storage slot 0
  value: 1000000000000000000n,  // stored value
  proof: [/* RLP-encoded trie nodes */],
});

Compare Proofs

const isEqual = StorageProof.equals(proof1, proof2);

API Reference

Constructors

FunctionDescription
from(proof)Create from StorageProofLike object

Methods

FunctionDescription
equals(a, b)Check if two proofs are equal

Obtaining Storage Proofs

Use eth_getProof with storage slots:
const result = await provider.send("eth_getProof", [
  contractAddress,
  ["0x0", "0x1", "0x2"],  // storage slots to prove
  "latest"
]);

// Extract storage proofs
const storageProofs = result.storageProof.map(sp => StorageProof.from(sp));

Storage Slot Calculation

Solidity storage layout determines slot numbers:
// Slot 0: first state variable
const slot0 = 0n;

// Mapping: keccak256(key . slot)
import * as Hash from './primitives/Hash/index.js';
const mappingSlot = Hash.keccak256(concat(
  padLeft(key, 32),
  padLeft(slot, 32)
));

// Dynamic array: keccak256(slot) + index
const arraySlot = BigInt(Hash.toHex(Hash.keccak256(padLeft(slot, 32)))) + index;

Verification Process

function verifyStorageProof(
  proof: StorageProofType,
  storageRoot: StateRootType
): boolean {
  // 1. Compute path = keccak256(key)
  const path = Hash.keccak256(proof.key);

  // 2. Walk Merkle Patricia Trie from root
  // 3. Verify proof nodes against path
  // 4. Extract value from leaf
  // 5. Compare with proof.value

  return true;
}

Use Cases

Verify Token Balance

// ERC-20 balanceOf mapping at slot 0
const balanceSlot = keccak256(concat(
  padLeft(holderAddress, 32),
  padLeft(0n, 32)  // balances mapping slot
));

const proof = await provider.send("eth_getProof", [
  tokenContract,
  [toHex(balanceSlot)],
  "latest"
]);

// Verify against trusted state root
const isValid = verifyStorageProof(
  proof.storageProof[0],
  proof.storageHash
);

console.log(`Verified balance: ${proof.storageProof[0].value}`);

Cross-Chain Storage Proof

// Prove L1 contract storage for L2 verification
const l1Proof = await l1Provider.send("eth_getProof", [
  l1Contract,
  [storageSlot],
  blockNumber
]);

// Submit to L2 bridge contract
await l2Bridge.verifyL1Storage(
  l1Contract,
  storageSlot,
  l1Proof.storageProof[0].proof,
  l1StateRoot
);

Oracle-Free Price Feeds

// Prove Uniswap pool reserves without oracle
const slot0 = "0x0";  // reserves slot

const proof = await provider.send("eth_getProof", [
  uniswapPool,
  [slot0],
  trustedBlockNumber
]);

// Verify and extract reserves
const reserves = decodeReserves(proof.storageProof[0].value);

Proof Structure

Account's Storage Trie
    |
    +-- storageRoot (32 bytes)
    |
    +-- Merkle Patricia Trie
        |
        +-- keccak256(slot0) --> value0
        |       proof[0]
        |
        +-- keccak256(slot1) --> value1
        |       proof[1]
        ...

Common Storage Slots

PatternSlot Calculation
Simple variableSequential from 0
Mappingkeccak256(key . slot)
Nested mappingkeccak256(key2 . keccak256(key1 . slot))
Dynamic array lengthslot
Dynamic array elementkeccak256(slot) + index

Specification

See Also