> ## Documentation Index
> Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# State Fundamentals

> Learn Ethereum state structure, Merkle Patricia Tries, and state verification

<Info>
  **Conceptual Guide** - For API reference and method documentation, see [State API](/primitives/state/index).
</Info>

Ethereum state is a global key-value mapping of all accounts (EOAs and contracts) to their data (balance, nonce, code, storage). This guide teaches state fundamentals using Tevm.

## What is Ethereum State?

Ethereum maintains a single, canonical **world state** - a mapping from 20-byte addresses to account data. This state changes with every transaction in every block.

```typescript theme={null}
// Conceptually, state is:
Map<Address, Account>

// Where Account contains:
{
  balance: bigint,      // Account balance in wei
  nonce: bigint,        // Transaction count (EOAs) or contract creation count (contracts)
  storageRoot: Hash,    // Root of account's storage trie (contracts only)
  codeHash: Hash        // Keccak256 of contract bytecode (contracts only)
}
```

## Two Types of State

Ethereum distinguishes between **account state** (global) and **storage state** (per-contract):

* **Account State**: Maps addresses to account objects (balance, nonce, storageRoot, codeHash)
* **Storage State**: Maps 256-bit slots to 256-bit values within each contract

The StorageKey primitive combines these: `{address, slot}` uniquely identifies any storage location.

## Account State

Two account types exist in Ethereum:

<Tabs>
  <Tab title="Externally Owned Accounts (EOA)">
    ```typescript theme={null}
    import * as State from 'tevm/State';
    import * as Address from 'tevm/Address';
    import * as Hash from 'tevm/Hash';

    // EOA example (user wallet)
    const eoaAddress = Address("0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e");
    const eoaAccount = {
      balance: 1000000000000000000n,  // 1 ETH in wei
      nonce: 42n,                      // Has sent 42 transactions
      storageRoot: Hash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"),  // Empty storage
      codeHash: Hash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")     // Empty code
    };

    // EOAs have:
    // - Empty storage (storageRoot = hash of empty trie)
    // - Empty code (codeHash = hash of empty string)
    // - Nonce tracks transaction count
    ```
  </Tab>

  <Tab title="Contract Accounts">
    ```typescript theme={null}
    import * as State from 'tevm/State';
    import * as Address from 'tevm/Address';
    import * as Hash from 'tevm/Hash';

    // Contract account (USDC on mainnet)
    const contractAddress = Address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
    const contractAccount = {
      balance: 0n,                     // Contract has no ETH
      nonce: 1n,                       // Has created 1 contract (proxy pattern)
      storageRoot: Hash("0x..."), // Root of storage trie with token balances, allowances
      codeHash: Hash("0x...")     // Keccak256 hash of deployed bytecode
    };

    // Contracts have:
    // - Non-empty storage (storageRoot = merkle root of state)
    // - Non-empty code (codeHash = hash of deployed bytecode)
    // - Nonce tracks created contracts (CREATE/CREATE2)
    ```
  </Tab>
</Tabs>

## Storage State

Each contract account has its own **storage trie** mapping 256-bit slot numbers to 256-bit values:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import * as State from 'tevm/State';
    import * as Address from 'tevm/Address';

    // USDC contract storage example
    const usdcAddress = Address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");

    // Storage slot 0: total supply (simple variable)
    const slot0 = State.StorageKey(usdcAddress, 0n);

    // Storage slot 1: balances mapping base slot
    const balancesBaseSlot = 1n;

    // To access balances[userAddress], compute:
    // slot = keccak256(abi.encode(userAddress, balancesBaseSlot))
    const userAddress = Address("0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e");
    const balanceSlot = computeMappingSlot(userAddress, balancesBaseSlot);
    const userBalanceKey = State.StorageKey(usdcAddress, balanceSlot);
    ```
  </Tab>
</Tabs>

### Solidity Storage Layout

Solidity compiles variables to storage slots following specific rules:

```solidity theme={null}
// USDC-like token contract
contract Token {
  uint256 public totalSupply;                 // Slot 0
  mapping(address => uint256) public balances;   // Slot 1 (base slot)
  mapping(address => mapping(address => uint256)) public allowances; // Slot 2
  address public owner;                       // Slot 3
  bool public paused;                         // Slot 4
}
```

```typescript theme={null}
import * as State from 'tevm/State';
import { Keccak256 } from 'tevm/crypto';

// Simple variables occupy sequential slots
const totalSupplyKey = State.StorageKey(contractAddress, 0n);
const ownerKey = State.StorageKey(contractAddress, 3n);
const pausedKey = State.StorageKey(contractAddress, 4n);

// Mappings: slot = keccak256(abi.encode(key, baseSlot))
function computeMappingSlot(key: Uint8Array, baseSlot: bigint): bigint {
  const encoded = Bytes64();
  encoded.set(new Uint8Array(32 - key.length).fill(0), 0); // Padding
  encoded.set(key, 32 - key.length);
  encoded.set(encodeBigInt(baseSlot, 32), 32);

  const hash = Keccak256.hash(encoded);
  return BigInt(Hex.fromBytes(hash));
}

// balances[userAddress]
const balanceSlot = computeMappingSlot(userAddressBytes, 1n);
const balanceKey = State.StorageKey(contractAddress, balanceSlot);

// allowances[owner][spender] - nested mapping
const ownerSlot = computeMappingSlot(ownerAddressBytes, 2n);
const allowanceSlot = computeMappingSlot(spenderAddressBytes, ownerSlot);
const allowanceKey = State.StorageKey(contractAddress, allowanceSlot);
```

## Merkle Patricia Trie Structure

Ethereum stores state in a **Merkle Patricia Trie** - a cryptographic data structure combining:

* **Merkle Tree**: Any change to data changes the root hash
* **Patricia Trie**: Space-efficient prefix tree for key-value lookups

### How It Works

```
State Trie (accounts):
Root Hash: 0x1234...
  ├─ Branch Node (prefix: 0x0)
  │   ├─ Extension Node (prefix: 0x07)
  │   │   └─ Leaf Node: Account(0x0742d35...) → {balance, nonce, storageRoot, codeHash}
  │   └─ Leaf Node: Account(0x0A0b869...) → {balance, nonce, storageRoot, codeHash}
  └─ Branch Node (prefix: 0x1)
      └─ Leaf Node: Account(0x1234567...) → {balance, nonce, storageRoot, codeHash}

Storage Trie (per-contract):
Root Hash: 0x5678...
  ├─ Branch Node (prefix: 0x0)
  │   └─ Leaf Node: Slot(0x0000...00) → 0x00000000000000000000000003b9aca00 (1,000,000,000)
  └─ Branch Node (prefix: 0xf)
      └─ Leaf Node: Slot(0xf1c2...3e) → 0x000000000000000000000000000186a0 (100,000)
```

Each node hashes its children, creating a merkle tree where:

* The **state root** proves the entire account state
* Each account's **storageRoot** proves that contract's storage

### State Root

The **state root** is a single 32-byte hash proving the entire world state:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import * as Hash from 'tevm/Hash';

    // Every block header includes state root
    const blockStateRoot = Hash("0x123abc..."); // 32 bytes

    // Changing ANY account data changes the state root
    // This enables:
    // - Light client verification
    // - State proofs
    // - Fraud proofs for rollups

    // Example: Transfer changes state root
    const beforeTransfer = Hash("0xaaa...");
    // ... execute transfer transaction ...
    const afterTransfer = Hash("0xbbb...");

    // State root changed because sender/receiver balances changed
    console.log(Hash.equals(beforeTransfer, afterTransfer)); // false
    ```
  </Tab>
</Tabs>

## Storage Key Operations

Tevm provides utilities for working with storage keys:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import * as State from 'tevm/State';
    import * as Address from 'tevm/Address';

    const contract = Address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");

    // Create storage keys
    const key1 = State.StorageKey(contract, 0n);
    const key2 = State.StorageKey(contract, 1n);

    // Compare keys
    console.log(State.StorageKey.equals(key1, key2)); // false
    console.log(State.StorageKey.equals(key1, key1)); // true

    // Serialize for map keys
    const map = new Map<string, bigint>();
    map.set(State.StorageKey.toString(key1), 100n);
    map.set(State.StorageKey.toString(key2), 200n);

    // String format: "address:slot"
    console.log(State.StorageKey.toString(key1));
    // "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48:0"

    // Deserialize from string
    const keyStr = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48:42";
    const parsed = State.StorageKey(keyStr);
    console.log(Address.toHex(parsed.address)); // "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
    console.log(parsed.slot);                    // 42n

    // Type guard
    if (State.StorageKey.is(key1)) {
      console.log("Valid storage key");
    }
    ```
  </Tab>
</Tabs>

## State Transitions

Every transaction modifies state. The EVM applies state transitions:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import * as Address from 'tevm/Address';
    import * as Hash from 'tevm/Hash';

    // State transition example: ETH transfer
    const sender = Address("0xAlice...");
    const receiver = Address("0xBob...");
    const amount = 1000000000000000000n; // 1 ETH
    const gasPrice = 20000000000n; // 20 gwei
    const gasUsed = 21000n;

    // Before transaction
    const stateBefore = new Map([
      [Address.toHex(sender), {
        balance: 2000000000000000000n,
        nonce: 5n,
        storageRoot: emptyStorageRoot,
        codeHash: emptyCodeHash
      }],
      [Address.toHex(receiver), {
        balance: 500000000000000000n,
        nonce: 0n,
        storageRoot: emptyStorageRoot,
        codeHash: emptyCodeHash
      }]
    ]);

    // Apply transaction
    function applyTransaction(state: Map<string, Account>, tx: Transaction) {
      const senderAddr = Address.toHex(tx.from);
      const receiverAddr = Address.toHex(tx.to);

      // 1. Check sender balance >= amount + gas
      const sender = state.get(senderAddr)!;
      const totalCost = tx.value + (tx.gasPrice * tx.gasLimit);
      if (sender.balance < totalCost) throw new Error("Insufficient balance");

      // 2. Increment sender nonce
      sender.nonce += 1n;

      // 3. Deduct amount + gas from sender
      sender.balance -= (tx.value + (tx.gasPrice * gasUsed));

      // 4. Add amount to receiver
      const receiver = state.get(receiverAddr)!;
      receiver.balance += tx.value;

      // 5. Refund unused gas (gasLimit - gasUsed)
      sender.balance += (tx.gasPrice * (tx.gasLimit - gasUsed));

      return state;
    }

    // After transaction
    const stateAfter = applyTransaction(stateBefore, {
      from: sender,
      to: receiver,
      value: amount,
      gasPrice,
      gasLimit: 21000n
    });

    // State root changes
    const oldRoot = computeStateRoot(stateBefore);
    const newRoot = computeStateRoot(stateAfter);
    console.log(`State root changed: ${!Hash.equals(oldRoot, newRoot)}`); // true
    ```
  </Tab>
</Tabs>

### Contract Storage Changes

Storage changes also trigger state root updates:

```typescript theme={null}
import * as State from 'tevm/State';

// SSTORE operation changes storage
const contractAddr = Address("0xContract...");
const storageKey = State.StorageKey(contractAddr, 0n);

// Before: slot 0 = 100
storage.set(State.StorageKey.toString(storageKey), 100n);
const storageBefore = computeStorageRoot(storage);

// After: slot 0 = 200 (SSTORE 0x00, 0xc8)
storage.set(State.StorageKey.toString(storageKey), 200n);
const storageAfter = computeStorageRoot(storage);

// Storage root changed
console.log(Hash.equals(storageBefore, storageAfter)); // false

// Contract's storageRoot in account state also changes
// Which changes the account trie
// Which changes the state root
```

## Merkle Proof Verification

Merkle proofs allow proving account or storage data without the full state:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import * as Hash from 'tevm/Hash';
    import { Keccak256 } from 'tevm/crypto';
    import * as Rlp from 'tevm/Rlp';

    // Merkle proof verifies account data against state root
    function verifyAccountProof(
      stateRoot: Hash,
      accountAddress: Address,
      accountData: Account,
      proof: Uint8Array[]
    ): boolean {
      // 1. Start with account data hash
      const accountRlp = Rlp.encode([
        accountData.nonce,
        accountData.balance,
        accountData.storageRoot,
        accountData.codeHash
      ]);
      let currentHash = Keccak256.hash(accountRlp);

      // 2. Walk up the trie using proof nodes
      for (const proofNode of proof) {
        const combined = new Uint8Array(proofNode.length + currentHash.length);
        combined.set(proofNode, 0);
        combined.set(currentHash, proofNode.length);
        currentHash = Keccak256.hash(combined);
      }

      // 3. Final hash should equal state root
      return Hash.equals(Hash(currentHash), stateRoot);
    }

    // Example usage
    const proof = [
      new Uint8Array([/* proof node 1 */]),
      new Uint8Array([/* proof node 2 */]),
      new Uint8Array([/* proof node 3 */])
    ];

    const isValid = verifyAccountProof(
      blockStateRoot,
      Address("0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e"),
      eoaAccount,
      proof
    );
    console.log(`Proof valid: ${isValid}`);
    ```
  </Tab>
</Tabs>

## Common Use Cases

### Light Client Verification

Light clients verify state without downloading full blockchain:

```typescript theme={null}
// Light client flow
async function verifyBalance(
  rpcUrl: string,
  address: Address,
  blockNumber: bigint
): Promise<{ balance: bigint, verified: boolean }> {
  // 1. Get block header (contains state root)
  const header = await fetch(`${rpcUrl}/eth_getBlockByNumber`, {
    method: 'POST',
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'eth_getBlockByNumber',
      params: [blockNumber.toString(16), false],
      id: 1
    })
  }).then(r => r.json());

  const stateRoot = Hash(header.result.stateRoot);

  // 2. Request account proof from full node (EIP-1186)
  const proof = await fetch(`${rpcUrl}/eth_getProof`, {
    method: 'POST',
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'eth_getProof',
      params: [Address.toHex(address), [], blockNumber.toString(16)],
      id: 2
    })
  }).then(r => r.json());

  // 3. Verify proof against state root
  const accountData = proof.result;
  const isValid = verifyAccountProof(
    stateRoot,
    address,
    accountData,
    proof.result.accountProof
  );

  return {
    balance: BigInt(accountData.balance),
    verified: isValid
  };
}
```

### Storage Proof for Token Balances

Prove token balance without trusting the RPC provider:

```typescript theme={null}
import * as State from 'tevm/State';

async function verifyTokenBalance(
  tokenAddress: Address,
  holderAddress: Address,
  blockNumber: bigint
): Promise<{ balance: bigint, verified: boolean }> {
  // 1. Compute storage slot for balances[holder]
  const balanceSlot = computeMappingSlot(
    Address.toBytes(holderAddress),
    1n // Assuming balances mapping is at slot 1
  );

  // 2. Get storage proof (EIP-1186)
  const proof = await fetch(`${rpcUrl}/eth_getProof`, {
    method: 'POST',
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'eth_getProof',
      params: [
        Address.toHex(tokenAddress),
        [balanceSlot.toString(16)],
        blockNumber.toString(16)
      ],
      id: 1
    })
  }).then(r => r.json());

  // 3. Verify storage proof
  const storageValue = BigInt(proof.result.storageProof[0].value);
  const isValid = verifyStorageProof(
    proof.result.storageHash,
    balanceSlot,
    storageValue,
    proof.result.storageProof[0].proof
  );

  return {
    balance: storageValue,
    verified: isValid
  };
}
```

### Optimistic Rollup Fraud Proofs

Rollups use state proofs for fraud detection:

```typescript theme={null}
// Fraud proof submission
async function submitFraudProof(
  l1StateRoot: Hash,
  l2Batch: Batch,
  invalidTx: Transaction
) {
  // 1. Prove pre-state against L1 state root
  const preStateProof = await generateStateProof(l1StateRoot, [
    invalidTx.from,
    invalidTx.to
  ]);

  // 2. Execute transaction locally
  const actualState = applyTransaction(preStateProof.state, invalidTx);
  const actualPostStateRoot = computeStateRoot(actualState);

  // 3. Compare with claimed post-state
  const claimedPostStateRoot = l2Batch.postStateRoot;

  if (!Hash.equals(actualPostStateRoot, claimedPostStateRoot)) {
    // Fraud detected! Submit proof on-chain
    await l1Contract.proveFraud({
      batchId: l2Batch.id,
      preStateRoot: l1StateRoot,
      preStateProof,
      transaction: invalidTx,
      claimedPostState: claimedPostStateRoot,
      actualPostState: actualPostStateRoot
    });

    // L1 contract verifies proof and slashes sequencer
  }
}
```

### Contract Storage Analysis

Analyze storage layout and usage:

```typescript theme={null}
import * as State from 'tevm/State';

// Enumerate storage slots
async function analyzeContractStorage(
  contractAddress: Address,
  provider: any
): Promise<Map<bigint, bigint>> {
  const storage = new Map<bigint, bigint>();

  // Storage slots are 2^256 possible values
  // In practice, query known slots or iterate from 0
  for (let slot = 0n; slot < 100n; slot++) {
    const key = State.StorageKey(contractAddress, slot);
    const value = await provider.send('eth_getStorageAt', [
      Address.toHex(contractAddress),
      '0x' + slot.toString(16).padStart(64, '0'),
      'latest'
    ]);

    const valueBigInt = BigInt(value);
    if (valueBigInt !== 0n) {
      storage.set(slot, valueBigInt);
      console.log(`Slot ${slot}: ${valueBigInt}`);
    }
  }

  return storage;
}

// Reverse engineer storage layout
const usdcStorage = await analyzeContractStorage(usdcAddress, provider);
// Slot 0: 1000000000 (totalSupply)
// Slot 3: 0x742d35... (owner address)
// Slot 4: 0 (paused = false)
```

## Resources

* **[Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf)** - Formal state specification (Section 4.1, Appendix D)
* **[ethereum.org State Docs](https://ethereum.org/en/developers/docs/accounts/)** - Account and state overview
* **[Patricia Merkle Trie Spec](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/)** - Trie structure details
* **[@ethereumjs/trie](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie)** - Production trie implementation
* **[evm.codes SLOAD/SSTORE](https://www.evm.codes/#54)** - Storage access opcodes
* **[EIP-1186](https://eips.ethereum.org/EIPS/eip-1186)** - eth\_getProof RPC method
* **[Solidity Storage Layout](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html)** - Storage slot computation rules

## Next Steps

* [Overview](/primitives/state) - Type definition and API reference
* [Storage Keys](/primitives/state/storage-key) - Creating storage keys
* [Merkle Trees](/primitives/state/merkle-trees) - Trie operations and verification
* [Usage Patterns](/primitives/state/usage-patterns) - Real-world examples
