EIP-7923 Linear page-based memory pricing (Linear, Page-Based Memory Costing)
Hegota Declined for Inclusion · EVM & execution · EIP status Draft · created 2025-03-27
Changes EVM memory cost from the current quadratic formula to linear pricing per page (4096 bytes). Each page touched for the first time costs 100 gas, and a hard cap of 64 MB applies to the whole transaction.
Why it is needed
The EVM uses a quadratic memory model in which cost grows roughly with the square of memory used. It was originally meant to prevent denial-of-service (DoS) attacks, but it now looks outdated. Even at a 30 million gas limit, a single message call can use only about 3MB of memory, and it has to burn all of its gas to do so. Because the quadratic term starts at 724 bytes, using just 64KB, the amount of memory in an early 1980s PC, costs 14,336 gas. Working out the maximum memory a transaction can use requires solving an optimization problem that includes the call stack limit and the 63/64 rule from EIP-150, so it is also hard to reason about. In addition, languages such as Vyper and Solidity cannot use the modern virtual memory layout in which the heap grows upward and the call stack grows downward, which makes their memory models inefficient. This EIP proposes a model like today's hardware: memory is allocated in pages only when needed, and the cost is charged linearly.
What changes
Today
- Memory cost has a quadratic term that makes it steeply expensive from 724 bytes, and using 64KB costs 14,336 gas
- Even at a 30 million gas limit, a single call can use only about 3MB of memory
- A transaction's maximum memory usage depends on the call stack state, which makes it hard to compute
- Vyper and Solidity cannot use virtual memory techniques that separate the heap from the call stack
After EIP-7923
- Billing becomes linear at 100 gas for each newly touched page (4096 bytes), and the first page is free
- A hard cap of 64MB (16,384 pages) applies to the whole transaction, and memory addresses are limited to 32 bits
- The memory limit no longer depends on the message call stack state, so it is immediately clear how much can be used
- Language implementations can use the virtual address space to split the heap and the call stack
Who is affected
- Contract and language developers: Code that uses a lot of memory becomes much cheaper, and Vyper and Solidity can implement a memory model that separates the heap from the call stack.
- Client teams: The quadratic expansion cost logic must be removed and a page tracking data structure added. On systems with mmap, it can be implemented simply with a 2^32-byte anonymous mapping.
- RPC providers: The memory cap is decoupled from the gas limit, so even when eth_call is served with a gas limit higher than mainnet, the risk of memory blowing up from a misconfiguration is reduced.
- Users: Calls that previously failed for lack of gas can now run to completion under the new billing.
In depth
Three changes to the billing algorithm
First, for each page an instruction touches, ALLOCATE_PAGE_COST (100 gas) is charged if it is touched for the first time in this message call. Page 0 is the exception and is free. Second, the base gas cost of all memory instructions, including MLOAD and MSTORE, stays at 3. Third, the linear and quadratic terms of the existing memory expansion cost are removed. The constants are ALLOCATE_PAGE_COST = 100, PAGE_SIZE = 4096, and MAXIMUM_MEMORY_SIZE = 64 * 1024 * 1024.
32-bit address limit and transaction-wide cap
Memory addresses are limited to 32 bits. If an address is greater than 2**32 - 1, an exceptional halt must be triggered. An exceptional halt also occurs if the number of pages allocated in a transaction exceeds MAXIMUM_MEMORY_SIZE // PAGE_SIZE, that is, 16384. This cap applies to the whole transaction, not to each message call. A per-call cap (for example 2MB) was also considered as an alternative, but the transaction-wide cap was chosen because it would not reduce implementation complexity much. The 2**32-byte address limit is chosen so that 64-bit computers on various operating systems and architectures can easily reserve that much virtual address space, and the document states that it can be revisited if the gas limit or client memory grows significantly.
MSIZE semantics stay the same
MSIZE behavior does not change. It returns the highest byte touched by memory instructions, rounded up to a multiple of 32. This choice preserves backward compatibility, because existing contracts assume MSIZE grows linearly. However, the original text recommends that contracts using the new virtual addressing approach should not rely on MSIZE for memory allocation.
Benchmark basis for the 100 gas price
Based on the fact that a 2019-era CPU can process keccak256 at about 256MB per second (6 gas per 32 bytes), the authors set a ratio of 20ns per gas and ran benchmarks. Allocating a new page took 1 to 2us, a random read within a 2MB range took 1.8ns, within a 32MB range 7ns, within a 4GB range 40ns, and an mmap system call 230ns. The 100 gas page allocation price came from these numbers. The cost of running mmap (about 11 gas) is already covered by the 100 gas base cost of CALL-family instructions, and for the same reason page 0 is left free. As a result, access within the first page costs the same or less than before, which also improves backward compatibility.
Two implementation paths
Systems without mmap implement virtual addressing themselves. They maintain a map[page_id -> char[4096]] keyed by page_id (the address shifted right by 12 bits as an integer), and reads and writes that cross a page boundary load both pages and merge the result. Systems with POSIX mmap (VirtualAlloc on Windows) have it easier. If a 2**32-byte region is reserved with an anonymous mmap, the operating system allocates only the pages actually touched, so memory reads and writes become simple accesses to this buffer. In this case, the pages map holds no data and only tracks which pages have been allocated for billing. The original text includes a reference implementation in the form of a patch of about 60 lines for py-evm.
Why the memory cap is separated from the gas limit
On the client implementation side, there is a demand to enforce a global limit separate from the gas limit to prevent DoS. For example, an RPC provider may be designed to handle many eth_call requests concurrently with a gas limit much higher than mainnet, and if the memory limit is not implicitly tied to the gas limit, one avenue for misconfiguration is removed. In the future, a formula could be created that raises the limit in line with hardware progress, for example in proportion to the square root of the gas limit, but the document explains that a hard limit was introduced for now to narrow the scope of this EIP's review.
Security and backward compatibility analysis
The original text examines two questions. First, do existing contracts break? Setting gas billing aside, existing contracts can only see executions that previously failed for lack of gas now run to completion, so backward compatibility is not broken. Second, does this enable memory DoS against clients? Even today, at a 30 million gas limit, a contract that allocates 256KB per call stack and calls itself recursively can allocate a total of 54MB, which is not very different from this EIP's 64MB cap. The transaction-wide cap also creates an invariant: even if the call stack limit changes in the future, the total memory a single transaction can use does not change.
Current status and fork discussion
This EIP is in Draft status, so the constants and detailed rules in the specification may still change. In the core developer calls, it was accepted as a Hegota fork candidate (PFI) at ACDE #243 on August 13, 2026, but was declined (DFI) at ACDE #245 on September 10, 2026. The judgment was that the improvement was recognized but not a priority, with the caveat that it could be reconsidered if capacity opens up.