> ## 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.

# SLOAD (0x54)

> Load word from persistent storage with cold/warm access tracking (EIP-2929)

<Warning>
  **This page is a placeholder.** All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.
</Warning>

## Overview

**Opcode:** `0x54`
**Introduced:** Frontier (EVM genesis)
**Updated:** Berlin (EIP-2929, cold/warm tracking)

SLOAD reads a 256-bit value from an account's persistent storage. The gas cost depends on whether the storage slot was previously accessed in the transaction (warm) or not (cold).

This operation is essential for reading contract state: balances, permissions, prices, and any data that must persist across transactions.

## Specification

**Stack Input:**

```
key (storage slot address)
```

**Stack Output:**

```
value (256-bit value at slot, or 0 if uninitialized)
```

**Gas Cost:**

* **100 gas** - Warm access (already accessed in transaction)
* **2100 gas** - Cold access (first access, EIP-2929)

**Operation:**

```
slot = pop()
value = storage[msg.sender][slot]  // 0 if slot never written
push(value)
```

## Behavior

SLOAD retrieves the current value stored at a key in the calling contract's account storage:

1. **Pop key** from stack (256-bit unsigned integer)
2. **Query host** for storage value at contract address + key
3. **Return value** from host (0 if slot never written or cleared)
4. **Push result** to stack
5. **Track access** for cold/warm metering (EIP-2929)
6. **Increment PC**

### Cold vs Warm Access

**First access in transaction (cold):** 2100 gas

```javascript theme={null}
SLOAD(key)  // 2100 gas - cold access, not yet seen
```

**Subsequent accesses (warm):** 100 gas

```javascript theme={null}
SLOAD(key)  // 2100 gas first time
SLOAD(key)  // 100 gas second time (warm)
```

**Access list (EIP-2930):** Can pre-warm slots

```javascript theme={null}
// Access list declaration in transaction
[{ address: contract, storageKeys: [key] }]
// SLOAD uses warm cost even on first access
```

### Uninitialized Slots

Slots never written return 0:

```javascript theme={null}
SLOAD(0xFFFFFFFF)  // Returns 0 (never written)
```

## Examples

### Basic Storage Read

```typescript theme={null}
import { sload } from '@voltaire/evm/storage';
import { createFrame } from '@voltaire/evm/Frame';
import { createMemoryHost } from '@voltaire/evm/Host';

const host = createMemoryHost();
const frame = createFrame({
  stack: [0x42n],  // Key to read
  gasRemaining: 3000n,
  address: contractAddr,
});

// Pre-populate storage
host.setStorage(contractAddr, 0x42n, 0x1337n);

// Execute SLOAD
const error = sload(frame, host);

console.log(frame.stack);        // [0x1337n]
console.log(frame.gasRemaining); // 2900n (3000 - 100 warm)
console.log(error);              // null
```

### Cold Access Tracking

```typescript theme={null}
const host = createMemoryHost();
host.setStorage(contractAddr, 0x42n, 0x1337n);
host.setStorage(contractAddr, 0x43n, 0x2222n);

// First access to slot 0x42 (cold)
let frame = createFrame({
  stack: [0x42n],
  gasRemaining: 3000n,
  address: contractAddr,
});
sload(frame, host);
console.log(frame.gasRemaining);  // 900n (3000 - 2100 cold)

// Second access to slot 0x42 (warm, cached)
frame = createFrame({
  stack: [0x42n],
  gasRemaining: 3000n,
  address: contractAddr,
});
sload(frame, host);
console.log(frame.gasRemaining);  // 2900n (3000 - 100 warm)

// First access to slot 0x43 (cold, different slot)
frame = createFrame({
  stack: [0x43n],
  gasRemaining: 3000n,
  address: contractAddr,
});
sload(frame, host);
console.log(frame.gasRemaining);  // 900n (3000 - 2100 cold)
```

### Mapping Access

```solidity theme={null}
// Smart contract mapping
mapping(address => uint256) public balances;

// Reading mapping[addr]
// Solidity computes: keccak256(abi.encode(addr, 1)) → storage slot
// Then SLOAD retrieves value at that slot

function getBalance(address user) public view returns (uint256) {
  return balances[user];  // SLOAD with computed key
}
```

### Nested Structures

```typescript theme={null}
// Reading from dynamic storage arrays
// array[index] → SLOAD with computed offset
// Requires: baseSlot + (32 * index) for 32-byte elements

const baseSlot = 5n;  // Array stored at slot 5
const index = 10n;
const storageKey = baseSlot + (32n * index);  // Compute slot

frame = createFrame({
  stack: [storageKey],
  gasRemaining: 3000n,
  address: contractAddr,
});
sload(frame, host);
```

## Gas Cost

**Cost Matrix:**

| Access Type | Gas  | EIP      | Notes                       |
| ----------- | ---- | -------- | --------------------------- |
| Warm        | 100  | EIP-2929 | Seen before in transaction  |
| Cold        | 2100 | EIP-2929 | First access in transaction |
| Pre-warmed  | 100  | EIP-2930 | Via access list             |

**Optimization:** SLOAD is \~21x more expensive on cold access. Batch accesses or use access lists for known storage reads.

## Edge Cases

### Uninitialized Slot

```typescript theme={null}
const frame = createFrame({
  stack: [0xDEADBEEFn],
  gasRemaining: 3000n,
  address: contractAddr,
});

// Slot never written - returns 0
sload(frame, host);
console.log(frame.stack);  // [0n]
```

### Max Uint256 Value

```typescript theme={null}
const MAX = (1n << 256n) - 1n;
host.setStorage(contractAddr, 0x1n, MAX);

const frame = createFrame({
  stack: [0x1n],
  gasRemaining: 3000n,
  address: contractAddr,
});

sload(frame, host);
console.log(frame.stack);  // [MAX]
```

### Stack Boundaries

```typescript theme={null}
// Reading with key = 0
const frame = createFrame({
  stack: [0n],  // Slot 0
  gasRemaining: 3000n,
  address: contractAddr,
});
sload(frame, host);

// Reading with key = max
const maxFrame = createFrame({
  stack: [(1n << 256n) - 1n],
  gasRemaining: 3000n,
  address: contractAddr,
});
sload(maxFrame, host);
```

### Insufficient Gas

```typescript theme={null}
const frame = createFrame({
  stack: [0x42n],
  gasRemaining: 50n,  // < 100 (warm cost)
  address: contractAddr,
});

const error = sload(frame, host);
console.log(error);  // { type: "OutOfGas" }
console.log(frame.pc);  // 0 (unchanged, not executed)
```

## Common Usage

### State Variable Access

```solidity theme={null}
contract Counter {
  uint256 public count;  // Slot 0

  function getCount() public view returns (uint256) {
    return count;  // SLOAD(0)
  }

  function increment() public {
    count++;  // SLOAD(0) + ADD + SSTORE(0)
  }
}
```

### Mapping Lookups

```solidity theme={null}
contract Bank {
  mapping(address => uint256) public balances;

  function getBalance(address user) public view returns (uint256) {
    return balances[user];  // SLOAD with keccak computed key
  }

  function transfer(address to, uint256 amount) public {
    uint256 myBalance = balances[msg.sender];  // SLOAD cold (2100)
    require(myBalance >= amount, "insufficient balance");

    balances[msg.sender] = myBalance - amount;  // SLOAD warm (100)
    balances[to] += amount;
  }
}
```

### Multi-Read Optimization

```solidity theme={null}
// Inefficient: Multiple SLOAD cold accesses
function inefficient(address user) public view returns (uint256, uint256, uint256) {
  return (
    balances[user],     // SLOAD cold (2100)
    approved[user],     // SLOAD cold (2100)
    lastUpdate[user]    // SLOAD cold (2100)
  );
}

// Efficient: Cache in memory (MSTORE is cheap)
function efficient(address user) public view returns (uint256, uint256, uint256) {
  uint256 bal = balances[user];     // SLOAD cold (2100)
  uint256 app = approved[user];     // SLOAD cold (2100) - new slot
  uint256 last = lastUpdate[user];  // SLOAD cold (2100) - new slot
  return (bal, app, last);
}
```

### Access List Warm-up

```typescript theme={null}
// Solidity: Declare access list in transaction
const tx = {
  to: contractAddr,
  data: encodeFunctionCall("transfer", [to, amount]),
  accessList: [
    {
      address: contractAddr,
      storageKeys: [
        0x0n,  // balances mapping base
        0x1n,  // approved mapping base
      ]
    }
  ]
};

// SLOAD now uses 100 gas (warm) even on first access
```

## Implementation

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import * as Frame from "../../Frame/index.js";
    import { ColdSload } from "../../../primitives/GasConstants/BrandedGasConstants/constants.js";

    /**
     * SLOAD (0x54) - Load word from storage
     *
     * Stack:
     *   in: key
     *   out: value
     *
     * Gas: 100 (warm) or 2100 (cold) - EIP-2929
     */
    export function sload(frame, host) {
      // Pop key from stack
      const keyResult = Frame.popStack(frame);
      if (keyResult.error) return keyResult.error;
      const key = keyResult.value;

      // Note: EIP-2929 access list tracking for warm/cold slots
      // For now, assume cold access (worst case)
      const gasCost = ColdSload;

      const gasError = Frame.consumeGas(frame, gasCost);
      if (gasError) return gasError;

      // Load from storage via host
      const value = host.getStorage(frame.address, key);

      // Push value onto stack
      const pushError = Frame.pushStack(frame, value);
      if (pushError) return pushError;

      frame.pc += 1;
      return null;
    }
    ```
  </Tab>
</Tabs>

## Testing

### Test Coverage

```typescript theme={null}
import { describe, it, expect } from 'vitest';
import { sload } from './0x54_SLOAD.js';
import { createFrame } from '../../Frame/index.js';
import { createMemoryHost } from '../../Host/createMemoryHost.js';
import { from as addressFrom } from '../../../primitives/Address/index.js';

describe('SLOAD (0x54)', () => {
  it('loads value from storage', () => {
    const host = createMemoryHost();
    const addr = addressFrom("0x1234567890123456789012345678901234567890");
    host.setStorage(addr, 0x42n, 0x1337n);

    const frame = createFrame({
      stack: [0x42n],
      gasRemaining: 10000n,
      address: addr,
    });

    expect(sload(frame, host)).toBeNull();
    expect(frame.stack).toEqual([0x1337n]);
    expect(frame.pc).toBe(1);
  });

  it('loads zero for uninitialized storage', () => {
    const host = createMemoryHost();
    const addr = addressFrom("0x1234567890123456789012345678901234567890");

    const frame = createFrame({
      stack: [0xFFFFFFFFFn],
      gasRemaining: 10000n,
      address: addr,
    });

    expect(sload(frame, host)).toBeNull();
    expect(frame.stack).toEqual([0n]);
  });

  it('isolates storage by address', () => {
    const host = createMemoryHost();
    const addr1 = addressFrom("0x1111111111111111111111111111111111111111");
    const addr2 = addressFrom("0x2222222222222222222222222222222222222222");

    host.setStorage(addr1, 0x42n, 0xAAAAn);
    host.setStorage(addr2, 0x42n, 0xBBBBn);

    let frame = createFrame({
      stack: [0x42n],
      gasRemaining: 10000n,
      address: addr1,
    });
    expect(sload(frame, host)).toBeNull();
    expect(frame.stack).toEqual([0xAAAAn]);

    frame = createFrame({
      stack: [0x42n],
      gasRemaining: 10000n,
      address: addr2,
    });
    expect(sload(frame, host)).toBeNull();
    expect(frame.stack).toEqual([0xBBBBn]);
  });

  it('consumes 2100 gas on cold access', () => {
    const host = createMemoryHost();
    const addr = addressFrom("0x1234567890123456789012345678901234567890");
    host.setStorage(addr, 0x42n, 0x1337n);

    const frame = createFrame({
      stack: [0x42n],
      gasRemaining: 5000n,
      address: addr,
    });

    expect(sload(frame, host)).toBeNull();
    expect(frame.gasRemaining).toBe(2900n);  // 5000 - 2100
  });

  it('returns StackUnderflow on empty stack', () => {
    const host = createMemoryHost();
    const frame = createFrame({
      stack: [],
      gasRemaining: 10000n,
      address: addressFrom("0x1111111111111111111111111111111111111111"),
    });

    expect(sload(frame, host)).toEqual({ type: "StackUnderflow" });
  });

  it('returns OutOfGas when insufficient gas', () => {
    const host = createMemoryHost();
    const frame = createFrame({
      stack: [0x42n],
      gasRemaining: 50n,
      address: addressFrom("0x1111111111111111111111111111111111111111"),
    });

    expect(sload(frame, host)).toEqual({ type: "OutOfGas" });
  });

  it('returns StackOverflow when stack full', () => {
    const host = createMemoryHost();
    const fullStack = new Array(1024).fill(0n);

    const frame = createFrame({
      stack: fullStack,
      gasRemaining: 10000n,
      address: addressFrom("0x1111111111111111111111111111111111111111"),
    });

    expect(sload(frame, host)).toEqual({ type: "StackOverflow" });
  });
});
```

## Security

### State Immutability During Reads

SLOAD is read-only and safe in any context (even static calls). It cannot modify state, only query it:

```solidity theme={null}
function read(address user) public view returns (uint256) {
  return balances[user];  // Always safe, read-only
}
```

### Reentrancy Vulnerability (When Used with State Changes)

SLOAD itself is safe, but reading and then writing creates reentrancy windows:

```solidity theme={null}
// VULNERABLE: Read-check-write pattern
function transfer(address to, uint256 amount) public {
  uint256 balance = balances[msg.sender];  // SLOAD
  require(balance >= amount);

  balances[msg.sender] = balance - amount;  // SSTORE

  (bool ok, ) = to.call("");  // REENTERS HERE
  // Attacker re-enters before balance updated
}

// SAFE: Checks-Effects-Interactions pattern
function transfer(address to, uint256 amount) public {
  require(balances[msg.sender] >= amount);
  balances[msg.sender] -= amount;  // SSTORE first
  (bool ok, ) = to.call("");  // Reenter safely
  require(ok, "transfer failed");
}
```

### Access List Validation

Ensure access lists match actual storage accessed:

```typescript theme={null}
// Declared in access list
accessList: [{ address: token, storageKeys: [slot0, slot1] }]

// But code accesses different slot
host.getStorage(token, 0xFF)  // Slot 0xFF not in access list!
// Will cost 2100 gas instead of expected 100
```

### Gas Cost Variation

Cold/warm access affects gas accounting for batch operations:

```solidity theme={null}
function batchTransfer(address[] calldata users, uint256[] calldata amounts) public {
  for (uint i = 0; i < users.length; i++) {
    uint256 bal = balances[users[i]];  // Variable cost!
    // First unique user: 2100 gas (cold)
    // Repeated user: 100 gas (warm)
  }
}
```

## Benchmarks

**Access cost comparison:**

* Warm SLOAD: 100 gas
* Cold SLOAD: 2100 gas (21x more expensive)
* MLOAD (memory): 3 gas (67x cheaper than warm)
* L1 cache: \~0.5ns vs \~100ns for cold storage

**Practical implications:**

```typescript theme={null}
// Reading 100 values from same slot
SLOAD(key)  // 2100 gas first time
// Keep in stack/memory (cheap operations)
// 99 more stack operations at 3 gas each ≈ 297 gas total
// Total: 2100 + 297 = 2397 gas

// vs reading from cold storage 100 times
// 100 × 2100 = 210,000 gas (86x more expensive!)
```

## References

* [EVM Codes - SLOAD (0x54)](https://www.evm.codes/#54)
* [EIP-2929: Gas-cost increases for state access opcodes](https://eips.ethereum.org/EIPS/eip-2929)
* [EIP-2930: Optional access lists](https://eips.ethereum.org/EIPS/eip-2930)
* [Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf) - Section 9.3 (Account Storage)
* [Solidity Storage Layout](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html)
