What if your storage model matched your hardware?

Serving a 32-byte storage read may require page-sized backend I/O.
MIP-8 makes the EVM account for that page-sized reality.

The information on this page should not be quoted. Please refer to MIP-8 for the authoritative spec.

waiting...

128 slots × 32 bytes = 4,096 bytes = 1 page

127 sibling slots stay unused in this example

MIP-8 is live

Monad mainnet activated MIP-8 with MONAD_TEN on September 2, 2026 at 14:30 UTC. This read-only check verifies the active gas schedule on mainnet and testnet without sending a transaction.

Checking Mainnet

Checking Testnet

Same struct, different cost model

Solidity lays out struct fields at contiguous slots, but trie/backend hashing can scatter them across different physical locations. In this worst-case illustration, each field lands on a separate backend page. MIP-8 groups contiguous slots into one page.

Click each field to load it and compare the gas cost side by side. Cold read costs 8,100 gas, warm read costs 100 gas on Monad.

struct Token { owner, balance, timestamp, approved }

No fields loaded yet. Load fields to compare the pre-MIP-8 model with the current MIP-8 schedule.

Pre-MIP-8

0 cold reads

0 x 8,100

0

MIP-8 (current)

0

0

0

Compare the cost

Select a scenario to compare the pre-MIP-8 slot-based model with Monad's current page-aware model.

These read-only examples compare the storage-access component: 8,100 gas for the first read from a page and 100 gas for each subsequent read. They are not total transaction-gas estimates.

Loading 4 contiguous struct fields that fit in one page

Pre-MIP-8

32,400

storage-access gas

4 × 8,100 (distinct cold slots before MIP-8)

MIP-8 (current)

8,400

storage-access gas

1 × 8,100 (first page touch) + 3 × 100 (warm reads in same page)

Reference data structures

Reusable Solidity primitives that make MIP-8 page boundaries explicit instead of relying on accidental storage alignment.

These implementations optimize locality when one call touches several related values. A single lookup is not inherently cheaper, and every net-new slot still pays state-growth cost.

Dense bitmap

32,768 bits per page

slot 0128 slots · one pageslot 127

Stores consecutive 256-bit buckets in consecutive slots, avoiding the locality loss of mapping-backed bitmap buckets.

Good for: claim flags, permissions, dense IDs, epochs

Mip8DenseBitmap.layout(CLAIMS)
    .set(claimId);
View Solidity source

Uint256 vector

127 values in page zero

slot 0128 slots · one pageslot 127

Keeps length and the first 127 values together, then fills every following page with 128 sequential values. Includes batch append and range reads.

Good for: scores, observations, append-heavy records

Mip8Uint256Vector.layout(SCORES)
    .pushMany(values);
View Solidity source

Ring buffer

126 queued values per page

slot 0128 slots · one pageslot 127

Fits head, length, and a bounded FIFO into exactly one page, so push, pop, peek, and wraparound stay page-local.

Good for: recent prices, rolling observations, bounded work queues

Mip8RingBuffer.layout(ORDERS)
    .push(orderId);
View Solidity source

Keyed page

128 fields per entity

slot 0128 slots · one pageslot 127

Derives an independently aligned page from each logical key, keeping one account, market, or position record page-local.

Good for: account records, markets, positions, protocol parameters

Mip8KeyedPage.layout(ACCOUNTS, user)
    .set(BALANCE_FIELD, balance);
View Solidity source

Record slab

126 reusable records per page

slot 0128 slots · one pageslot 127

Combines an occupancy bitmap, live count, and fixed record slots. Removed records free their index for the next insertion.

Good for: orders, jobs, game entities, bounded allocators

uint256 orderIndex = Mip8Slab
    .layout(ORDERS).insert(orderId);
View Solidity source

Packed uint64 vector

508 values in page zero

slot 0128 slots · one pageslot 127

Packs four 64-bit values into each word. Later pages hold 512 values while updates preserve every neighboring packed value.

Good for: timestamps, counters, compact prices, numeric IDs

Mip8Uint64Vector.layout(PRICES)
    .pushMany(observations);
View Solidity source

Small blob

4,064 bytes per page

slot 0128 slots · one pageslot 127

Stores one bounded bytes value with its length and payload in a single page. Shorter rewrites clear obsolete trailing words.

Good for: encoded configs, metadata, proofs, bounded payloads

Mip8SmallBlob.layout(CONFIG)
    .write(encodedConfig);
View Solidity source

Each primitive uses a unique namespaced storage base with its lower seven bits cleared, guaranteeing that slot zero begins at a MIP-8 page boundary.

Read usage docs

Watch storage accesses in real time

Step through real contract code line by line. Each SLOAD/SSTORE lights up the corresponding storage slot and shows whether it's a cold or warm access under MIP-8.

Totals include only the 8,100/100 storage-access component. For SSTORE, they exclude the 2,800 first-write charge and any 17,000 state-growth charge.

Pair storage slots share one page, but balanceOf() reads execute in two separate token contracts. This simplified trace treats each balance page as untouched, so both token reads start cold; earlier token calls in a full transaction could warm them.

Uniswap V2 swap()

0 unique slots accessed

1
// UniswapV2Pair.sol
2
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
3
// lock modifier
4
require(unlocked == 1);  unlocked = 0;
5
 
6
(uint112 _reserve0, uint112 _reserve1,) = getReserves();
7
require(amount0Out > 0 || amount1Out > 0);
8
require(amount0Out < _reserve0 && amount1Out < _reserve1);
9
 
10
address _token0 = token0;
11
address _token1 = token1;
12
// transfers/callback omitted; token balance pages start untouched
13
uint balance0 = IERC20(_token0).balanceOf(address(this));
14
uint balance1 = IERC20(_token1).balanceOf(address(this));
15
 
16
// _update
17
price0CumulativeLast += ...;
18
price1CumulativeLast += ...;
19
 
20
// end lock modifier
21
unlocked = 1;
22
}

Uniswap V2 swap() is ready. 9 storage operations are available.

Design for pages, cut access gas 10X+

MIP-8 opens a new design space where page-aware storage can reduce the storage-access component by an order of magnitude.

Consider an ERC-1155 multi-token contract. The standard implementation hashes each token balance to a random storage location. In this example, an aligned page-aware design stores balances contiguously, so batch operations read one page instead of N scattered slots.

Standard ERC-1155

// balances scattered by keccak256

mapping(uint256 =>

mapping(address => uint256))

balances;

Each token ID hashes to a different page

20 tokens = 20 cold reads = 162,000 storage-access gas

Page-aware design

// balances packed contiguously

uint256[128] balances; // base slot 0

// as the first state field, slots 0-127 form one page

Aligned balances fit in one page

20 tokens = 1 cold + 19 warm = 10,000 storage-access gas

Number of token balances read in one operation

20

tokens

212 (10X threshold)64

20 tokens use 162,000 storage-access gas in the standard layout and 10,000 storage-access gas in the page-aware layout, saving 94% of the storage-access component.

Standard layout

162,000

storage-access gas

20 x 8,100 (all cold)

Page-aware + MIP-8

10,000

storage-access gas

8,100 + 19 x 100

Improvement

16.2x

cheaper

94% storage-access gas saved

Slot → Page mapping

Every slot maps deterministically to a page. The math is simple: shift right by 7 bits to get the page, mask the low 7 bits to get the offset within it.

0127255383511

page_index(slot) = slot >> 7

0 >> 7 = 0

offset(slot) = slot & 0x7F

0 & 127 = 0

Storage slot 0 maps to page 0 at offset 0.

Try your own contract

Paste a GitHub repo URL or Solidity source to see how your contract's storage layout maps to pages.

Works best with small-to-medium repos. Large repos with many dependencies (e.g. Aave, Chainlink) may time out.

Try:

What this means for you

Structs get cheaper

Solidity stores struct members and array elements contiguously. Under MIP-8, a contiguous run that fits in one page is typically 1 cold page touch plus N - 1 warm slot accesses instead of N cold slot accesses.

Mappings change less

Mappings still derive storage locations from keccak256, so unrelated keys almost always land on different pages. MIP-8 rarely helps or hurts truly random access; it mostly rewards contiguous layouts.

New optimization patterns

Page-aware arrays, careful packing, and low-level layouts that keep related data inside the same 128-slot page open a new optimization space for page-aware gas costs.

Execution stays compatible

At the opcode level, execution semantics stay the same: SLOAD still returns 32 bytes and SSTORE still writes 32 bytes. What changes is the storage commitment/proof layer and the gas model, which become page-aware. The effective key space narrows from 2²⁵⁶ hashed slots to 2²⁴⁹ page indices.

Contracts that read consecutive storage slots often get cheaper because Solidity stores struct members, fixed arrays, and runs of dynamic-array elements contiguously once their base location is known. Mappings still use hashed locations, so mapping-heavy access patterns tend to change less. Contracts that hardcode storage-opcode gas assumptions are at risk, regardless of access pattern.

Each 4,096-byte page is committed with an induced BLAKE3-based subtree over occupied 64-byte slot pairs. Empty branches are bypassed, and a 128-bit occupancy bitmap seals the exact positions. The bitmap plus sibling hashes is at most 208 bytes, excluding the target value and the outer MPT proof.

Continue the discussion on Monad Forum

Questions, feedback, or a better idea? Weigh in on the forum thread.

Open forum thread