# EIP-8141 Frame Transaction: Proof of Concept > Reference implementation of native account abstraction through frame transactions on a modified Ethereum execution layer. ## Table of Contents - [Overview](#overview) - [EIP-8141 in Brief](#eip-8141-in-brief) - [Client Modifications](#client-modifications) - [Go-Ethereum (geth) Fork](#go-ethereum-geth-fork) - [Solidity Compiler (solc) Fork](#solidity-compiler-solc-fork) - [Case Study 1: ERC-20 Paymaster](#case-study-1-erc-20-paymaster) - [Case Study 2: Kernel — Modular Account](#case-study-2-kernel--modular-account) - [Gas Benchmarks](#gas-benchmarks) - [Running the PoC](#running-the-poc) - [Security Considerations](#security-considerations) - [Potential Improvements](#potential-improvements) - [Future Work](#future-work) ## Overview This repository is a **proof-of-concept implementation** of [EIP-8141 (Frame Transactions)](https://ethereum-magicians.org/t/frame-transaction/27617), a proposed new Ethereum transaction type that enables native account abstraction at the protocol level. The PoC includes: 1. **A modified go-ethereum (geth) client** with frame transaction processing, new opcodes, and ERC-7562-style mempool validation 2. **A modified Solidity compiler (solc)** with support for four new EVM opcodes 3. **Smart contract reference implementations** — from minimal accounts to fully modular architectures 4. **A local devnet** with E2E tests and gas benchmarks comparing against ERC-4337 equivalents ## EIP-8141 in Brief EIP-8141 introduces a new [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) transaction type (`0x06`) called a **Frame Transaction**. Instead of relying on a single ECDSA signature, frame transactions allow accounts to define arbitrary validation, execution, and gas payment logic using EVM code. **Motivations:** Post-quantum signature support, native account abstraction without intermediary contracts (like ERC-4337's EntryPoint), and user-defined gas payment. ### Transaction Structure A frame transaction contains an **explicit sender** address and an ordered array of **frames** — each with `[mode, target, gas_limit, data]`: | Mode | Name | Caller | Purpose | |------|------|--------|---------| | 0 | `DEFAULT` | `ENTRY_POINT` (0xaa) | General execution (hooks, deployment, post-ops) | | 1 | `VERIFY` | `ENTRY_POINT` (0xaa) | Read-only validation (`STATICCALL`); must terminate with `APPROVE` | | 2 | `SENDER` | `tx.sender` | Execute on behalf of the account (requires prior approval) | **Key constraints:** - VERIFY frames execute as `STATICCALL` — no `sstore` / `tstore` / `LOG` - VERIFY frame calldata is **elided from the signature hash** (the signature cannot be part of what it signs) - SENDER frames require prior `APPROVE` from a VERIFY frame - Transient storage is discarded between frames ### New Opcodes | Opcode | Name | Purpose | |--------|------|---------| | `0xaa` | `APPROVE` | Terminate execution with a scope-based approval signal (execution / payment / both) | | `0xb0` | `TXPARAMLOAD` | Load a 32-byte word at an offset from a transaction parameter (like `CALLDATALOAD`) | | `0xb1` | `TXPARAMSIZE` | Get the byte size of a transaction parameter (like `CALLDATASIZE`) | | `0xb2` | `TXPARAMCOPY` | Copy arbitrary-length transaction parameter data to memory (like `CALLDATACOPY`) | `TXPARAMLOAD` enables **cross-frame introspection** — any frame can read another frame's calldata, enabling patterns like read-only parameter validation in VERIFY frames or post-execution checks in DEFAULT frames. ### Gas Accounting Each frame has an independent gas allocation. Unused gas does not carry over between frames. After all frames execute, total unused gas is refunded to the payer. Intrinsic cost is 15,000 gas (vs 21,000 for legacy transactions). ## Client Modifications ### Go-Ethereum (geth) Fork The geth fork (`8141-geth/`) implements the full EIP-8141 specification. Key changes: #### New Transaction Type A `FrameTx` struct in `core/types/tx_frame.go`: ```go type FrameTx struct { ChainID *uint256.Int Nonce uint64 Sender common.Address // Explicit sender (no signature) Frames []Frame // Ordered execution steps GasTipCap *uint256.Int GasFeeCap *uint256.Int BlobFeeCap *uint256.Int BlobHashes []common.Hash } type Frame struct { Mode uint8 // 0=DEFAULT, 1=VERIFY, 2=SENDER Target *common.Address // nil = tx.sender GasLimit uint64 Data []byte } ``` #### State Transition Engine In `core/state_transition.go`, the frame execution loop: 1. Validates nonce and static constraints 2. Iterates through frames, setting caller based on mode 3. Executes VERIFY frames via `StaticCall` (enforcing read-only) 4. Tracks approval state (sender/payer) 5. Enforces ordering rules (execution before payment, no double-approval) 6. Handles gas accounting with per-frame limits 7. Discards transient storage between frames while sharing warm/cold journal #### Opcode Implementation In `core/vm/instructions_frame.go` and `core/vm/eips.go`: - **APPROVE**: Sets `evm.ApproveScope` and terminates (same gas model as `RETURN`) - **TXPARAMLOAD**: 5 gas — loads from `FrameContext` struct on the EVM - **TXPARAMSIZE**: 3 gas — returns parameter size - **TXPARAMCOPY**: 3 gas + memory expansion + copy cost (same model as `CALLDATACOPY`) #### Frame Mempool A dedicated mempool for frame transactions with DoS protections: - 4 pending transactions per sender - 256 total pooled transactions - 512 KiB maximum transaction size *Note: EIP-8141 does not specify a dedicated mempool. The separate `framepool` is an implementation choice following geth's existing pattern (`legacypool`, `blobpool`), motivated by the fundamentally different validation flow (EVM simulation with ERC-7562 tracer vs. simple signature/balance checks).* #### ERC-7562 Validation Tracer In `core/vm/frame_validation_tracer.go`, a lightweight fail-fast tracer enforces [ERC-7562](https://eips.ethereum.org/EIPS/eip-7562)-style mempool validation rules during VERIFY frame simulation. The tracer records the first rule violation and short-circuits all subsequent processing. ##### Implemented Rules | Rule | Category | Description | |------|----------|-------------| | **OP-011** | Banned opcodes | `ORIGIN`, `GASPRICE`, `BLOCKHASH`, `COINBASE`, `TIMESTAMP`, `NUMBER`, `PREVRANDAO`, `GASLIMIT`, `BASEFEE`, `BLOBHASH`, `BLOBBASEFEE`, `CREATE`, `CREATE2`, `SELFDESTRUCT`, `INVALID` | | **OP-012** | GAS rule | `GAS` opcode must be immediately followed by a CALL-type opcode (`CALL`, `CALLCODE`, `DELEGATECALL`, `STATICCALL`). Prevents gas-limit probing. | | **OP-020** | Out-of-gas | Out-of-gas reverts in VERIFY frames are forbidden. Prevents gas metering attacks that invalidate transactions based on gas limits. | | **OP-041** | Code existence | `EXTCODESIZE`, `EXTCODECOPY`, `EXTCODEHASH`, and CALL-type opcodes require the target address to have deployed code. | | **OP-042** | Sender exception | The sender's own address is exempt from the OP-041 code existence check. | | **OP-080** | Balance access | `BALANCE` and `SELFBALANCE` are banned (tracked separately from OP-011 for distinct error reporting). | | **STO-010** | Own storage | The sender's own contract storage is always readable without restriction. | | **STO-021** | Associated storage | External contract storage reads must pass an "associated storage" check (see below). | | **STO-031** | Entity storage | The VERIFY frame target's own contract storage is always readable (adapted from ERC-7562 STO-031; unconditional, no staking required, only for PoC). | **Associated storage detection (STO-021)** uses a two-pass algorithm: 1. **During execution**: The tracer collects all KECCAK256 preimages (up to 128 bytes) and records external SLOAD operations. 2. **Post-execution**: Each external storage access is validated against two criteria: - *Direct value match*: The slot's current value equals the sender address (left-padded to 32 bytes) - *Keccak derivation*: The slot equals `keccak256(sender || x) + n` where `n ∈ [0, 128]`, matching the Solidity `mapping(address => ...)` storage pattern and struct field offsets ##### Rules Not Implemented (and Why) | ERC-7562 Rule | Reason for Omission | |---------------|---------------------| | **SSTORE / LOG0-4 / CALL with value** | Already blocked by STATICCALL. VERIFY frames execute as `StaticCall`, so the EVM itself rejects state-modifying opcodes. Redundant enforcement would add complexity with no benefit. | | **TSTORE / TLOAD** | Same rationale — STATICCALL prevents transient storage writes. Additionally, transient storage is discarded between frames at the state-transition level. | | **Call depth limit (OP-030)** | The EVM's built-in 1024-depth limit applies inherently. No explicit tracer enforcement needed. | | **Staking/reputation rules (EREP-xxx)** | ERC-7562's entity reputation system is designed for the ERC-4337 bundler ecosystem (EntryPoint, paymasters, factories, aggregators). EIP-8141 eliminates the bundler role entirely — frame transactions are submitted directly to the mempool like regular transactions, making reputation tracking unnecessary. | | **Aggregator rules (OP-050, OP-060)** | No aggregator concept in EIP-8141. Signature aggregation is not yet specified. | | **Factory/initCode rules (OP-031, OP-032)** | EIP-8141 has no `initCode` field. Account deployment uses DEFAULT frames with CREATE2, which are unrestricted (not subject to VERIFY frame rules). | | **Paymaster-specific rules (PM-xxx)** | Paymasters in EIP-8141 are regular VERIFY frame targets — `APPROVE(scope=1)` replaces the separate paymaster validation/postOp lifecycle. The VERIFY frame target's own storage is exempt from STO-021 via STO-031, matching ERC-7562's entity storage exemption without requiring a staking mechanism. | The approach relies on STATICCALL's inherent constraints rather than re-implementing them in the tracer, and omits ERC-4337 bundler-specific rules that don't apply to EIP-8141's direct-submission model. ### Solidity Compiler (solc) Fork The solc fork (`solidity-eip8141/`) adds compile-time support for the four new opcodes. #### Instruction Definitions In `libevmasm/Instruction.h`: ```cpp APPROVE = 0xaa, // halt execution with scope-based approval TXPARAMLOAD = 0xb0, // load transaction parameter data TXPARAMSIZE = 0xb1, // get size of transaction parameter TXPARAMCOPY = 0xb2, // copy transaction parameter data to memory ``` #### Key Implementation Areas | Area | File | Changes | |------|------|---------| | Instruction enum | `libevmasm/Instruction.h` | Four new opcodes with stack I/O definitions | | Gas metering | `libevmasm/GasMeter.cpp` | APPROVE uses RETURN model; TXPARAMCOPY uses CALLDATACOPY model | | Semantic info | `libevmasm/SemanticInformation.cpp` | APPROVE: control-flow terminator, memory read. TXPARAMCOPY: memory write | | EVM version | `liblangutil/EVMVersion.h` | `hasFrameTransaction()` gated to Osaka+ | | Yul dialect | `libyul/backends/evm/EVMDialect.cpp` | Reserved identifier exceptions for pre-Osaka code | | Yul analysis | `libyul/AsmAnalysis.cpp` | Compiler errors 8150-8153 for pre-Osaka usage | #### Usage in Solidity The opcodes are available via inline assembly: ```solidity // APPROVE: terminate with approval assembly { approve(offset, length, scope) } // TXPARAMLOAD: load transaction parameter assembly { result := txparamload(in1, in2, offset) } // TXPARAMSIZE: get parameter size assembly { size := txparamsize(in1, in2) } // TXPARAMCOPY: copy parameter data to memory assembly { txparamcopy(in1, in2, destOffset, offset, size) } ``` Build configuration requires: ```toml solc = "path/to/custom/solc" evm_version = "osaka" via_ir = true ``` ## Case Study 1: ERC-20 Paymaster **File**: `contracts/src/ERC20Paymaster.sol` (~132 lines) The ERC-20 paymaster demonstrates how **token-based gas sponsorship** works under the frame transaction model. Users pay gas fees with ERC-20 tokens instead of ETH; the paymaster holds ETH to cover protocol-level gas costs and collects tokens at a configurable exchange rate. ### Frame Structure ``` Frame 0: VERIFY(sender) → account.validate(sig, scope=0) → APPROVE(EXECUTION) Frame 1: VERIFY(paymaster) → paymaster.validate() → APPROVE(PAYMENT) Frame 2: SENDER(erc20) → token.transfer(paymaster, amount) Frame 3: SENDER(account) → account.execute(target, value, data) Frame 4: DEFAULT(paymaster) → paymaster.postOp(2) ``` The 5-frame pattern separates each concern into its own frame: the account approves execution (Frame 0), the paymaster approves payment (Frame 1), the token transfer happens on behalf of the sender (Frame 2), the user's intended call executes (Frame 3), and the paymaster checks that the transfer frame did not revert (Frame 4). ### Cross-Frame Validation The paymaster's VERIFY frame (Frame 1) uses `TXPARAMLOAD` to read Frame 2's calldata and verify: 1. The ERC-20 `transfer(address,uint256)` selector 2. The recipient is the paymaster itself 3. The token is accepted (has a non-zero exchange rate) 4. The transfer amount covers gas cost: `amount >= maxCost * exchangeRate / 1e18` 5. The sender has sufficient token balance This is the core value proposition of cross-frame introspection — the paymaster validates a future frame's parameters read-only, without executing the transfer or modifying state. ### STO-021 Compliance Under ERC-7562, unstaked entities can only read "associated storage" of the account in external contracts (STO-021) — slots derived via `keccak256(sender || x) + n`, matching the Solidity `mapping(address => ...)` pattern. Scalar storage slots (slot 0, 1, etc.) at external contracts do not pass this check. For demonstration purpose, this PoC adapts ERC-7562's entity storage exemption (STO-031): the VERIFY frame target's own storage is unconditionally readable without STO-021 restrictions, removing the need for a staking mechanism. Thus, in this PoC, the paymaster reads its own `exchangeRates[token]` mapping and `owner` slot freely without any staking. This should be clarified in the future with exact mempool validation rules. ### Key Limitation: Paymaster Overcharges Users In ERC-4337, the EntryPoint passes `actualGasCost` to the paymaster's `postOp()`, enabling precise token refunds — the user is pre-charged for maximum gas, and excess tokens are returned based on actual consumption. **EIP-8141 has no equivalent mechanism.** The paymaster must charge the user for the full `maxCost` upfront, while the protocol refunds unused ETH to the paymaster: - User pays: `maxCost × rate` in ERC-20 tokens - Paymaster spends: `actualCost` in ETH (protocol refunds `maxCost - actualCost` ETH) - **Paymaster keeps the difference**: `(maxCost - actualCost)` worth of excess value If a transaction uses 50K gas out of a 200K budget, the user pays 200K worth of tokens while the paymaster only spends 50K in ETH — profiting from the 150K gap. This is a structural consequence of EIP-8141's frame isolation: the DEFAULT frame (where `postOp` runs) cannot observe actual gas consumed by previous frames, so in-transaction refunds are impossible. Mitigations include off-chain settlement (the paymaster tracks overcharges and refunds in batch transactions) or tighter gas limit estimation by the client SDK. See [Potential Improvements](#potential-improvements) for a proposed protocol-level extension and its trade-offs. See [Potential Improvements](#potential-improvements) for a proposed extension that would enable refunds. ## Case Study 2: Kernel — Modular Account **File**: `contracts/src/example/kernel/Kernel8141.sol` (~750 lines) Kernel8141 is a full port of [Kernel v3](https://github.com/zerodevapp/kernel) — a modular smart account architecture where the frame model's constraints most significantly affect the design. ### Architecture ``` Kernel8141 └── ValidationManager8141 (validator/permission/nonce/enable/EIP-712/ERC-1271) ├── SelectorManager8141 (fallback selector routing) ├── HookManager8141 (hook lifecycle) └── ExecutorManager8141 (executor registry) ``` The modular system supports pluggable **validators** (signature verification), **executors** (execution strategies), **hooks** (pre/post execution checks), **policies** (fine-grained access control), and **signers** (custom signing backends). ### The Hook Orchestration Problem In ERC-4337, the account contract orchestrates all module interactions within a single `handleOps` call — it decodes the nonce to resolve the validator, calls the validator inline, stores the hook reference via `sstore`, and later wraps execution with `hook.preCheck()` / `hook.postCheck()`. **EIP-8141 makes this impossible** because VERIFY frames are read-only (`STATICCALL`): 1. No `sstore` or `tstore` during validation — the account cannot run state-modifying hooks 2. Transient storage is discarded between frames — no cross-frame data passing via `tstore`/`tload` 3. Module installation requires `sstore` — but cannot happen in a VERIFY frame ### The Solution: Offchain Frame Orchestration I made Kernel8141 split the orchestration: **storage** stays in the account contract, but **invocation** moves to the transaction structure. The client (SDK/wallet) reads the account's configuration and constructs a frame transaction with explicit frames for each lifecycle step. ``` ERC-4337: Account orchestrates: validateUserOp → hook.preCheck → execute → hook.postCheck EIP-8141: Transaction orchestrates: [Hook Frame] → [Validate Frame] → [Execute Frame] ``` #### Frame Patterns **Simple transaction (no hook):** ``` Frame 0: VERIFY(kernel) → validate(sig, scope=2) → APPROVE(BOTH) Frame 1: SENDER(kernel) → execute(mode, data) ``` **With hook:** ``` Frame 0: DEFAULT(hook) → hook.check() Frame 1: VERIFY(kernel) → validate(sig, scope) → APPROVE Frame 2: SENDER(kernel) → execute(mode, data) ``` The hook runs in its own DEFAULT frame before validation. During the VERIFY frame, the kernel performs a structural check — it verifies that a DEFAULT frame targeting the required hook contract exists in the transaction (via `frameMode` and `frameTarget` introspection). Runtime status cannot be checked here because DEFAULT frames have not yet executed during VERIFY. The hook uses cross-frame introspection to read the SENDER frame's calldata and enforce policies. **Enable mode (install + validate):** ``` Frame 0: VERIFY(kernel) → validateWithEnable(enableData, sig, scope) → APPROVE Frame 1: DEFAULT(kernel) → enableInstall(enableData, vId) Frame 2: SENDER(kernel) → execute(mode, data) ``` In ERC-4337 Kernel v3, enable mode happens inside `validateUserOp` — the account calls `sstore` inline. In EIP-8141, this splits into a VERIFY frame for authentication and a DEFAULT frame for the actual `sstore`. The DEFAULT frame checks `_requirePriorVerifyApproval()` to prevent unauthorized installation. ### SpendingLimitHook Example The `SpendingLimitHook` demonstrates the frame-native hook pattern: ```solidity function check() external { address account = FrameTxLib.txSender(); uint256 senderIdx = _findSenderFrame(); uint256 totalValue = _extractValueFromSenderFrame(senderIdx); SpendingState storage state = spendingStates[account]; if (totalValue > available) revert DailyLimitExceeded(...); state.spentToday += totalValue; } ``` The hook identifies the account via `TXPARAMLOAD`, cross-reads the SENDER frame's calldata to extract the ETH value, and enforces the spending limit against persistent storage — all in its own frame, independent of the account's validation logic. ### Trade-offs The offchain orchestration pattern means clients must construct the correct frame sequence — complexity moves from the smart contract layer to the SDK/wallet layer. In return: - Hooks execute in independent frames with their own gas limits - Multiple hooks can be added as additional DEFAULT frames without modifying the account contract - The frame structure makes hook involvement visible at the transaction level --- ## Gas Benchmarks All benchmarks measured on the local devnet with cold recipients (first-time transfer to fresh addresses). This includes G_newaccount (25,000 gas) for ETH transfers and zero-to-non-zero SSTORE (~20,000 gas) for ERC-20 transfers. In addition to the two case studies above, I ported [Coinbase Smart Wallet](https://github.com/coinbase/smart-wallet) (multi-owner ECDSA + WebAuthn) and [Alchemy's LightAccount](https://github.com/alchemyplatform/light-account) (single-owner upgradeable) to EIP-8141 for gas comparison. ### EIP-8141 Results | Account | ETH Transfer | ERC-20 Transfer | |---|---:|---:| | Simple8141 | 58,017 | 56,760 | | LightAccount8141 | 64,612 | 63,486 | | CoinbaseSmartWallet8141 | 71,248 | 70,137 | | Kernel8141 | 74,911 | 73,425 | ### vs ERC-4337 ERC-4337 numbers from [ZeroDev aa-benchmark](https://github.com/zerodevapp/aa-benchmark) (single-ECDSA, L1 simulation): | Comparison | ERC-4337 | EIP-8141 | Reduction | |---|---:|---:|---:| | Simple → Simple8141 (ETH) | 101,319 | 58,017 | **43%** | | Kernel v2.1 → Kernel8141 (ETH) | 106,460 | 74,911 | **30%** | | LightAccount → LightAccount8141 (ETH) | 100,910 | 64,612 | **36%** | | Simple → Simple8141 (ERC-20) | 90,907 | 56,760 | **38%** | | Kernel v2.1 → Kernel8141 (ERC-20) | 96,038 | 73,425 | **24%** | | LightAccount → LightAccount8141 (ERC-20) | 90,411 | 63,486 | **30%** | The savings primarily come from removing the EntryPoint's fixed overhead (`handleOps` dispatch loop — calldata decoding, nonce management, stake checks, compensation transfer). ## Running the PoC For build instructions, devnet setup, and test commands, see the [README](https://github.com/sm-stack/eip8141-poc) in the repository root. ## Security Considerations ### This is a Research PoC This implementation is for research and demonstration purposes only. It has not been audited and should not be used in production. ## Potential Improvements ### Per-Frame Gas Accounting (`TXPARAMLOAD` Extension) The most significant gap in the current EIP-8141 spec for the paymaster ecosystem is the lack of per-frame gas consumption visibility. The [ERC-20 Paymaster](#case-study-1-erc-20-paymaster) must charge users for the full `maxCost` because actual gas used is not exposed to smart contracts — making ERC-20 token refunds impossible. There can be a new TXPARAM parameter that can expose per-frame actual gas consumption: ``` TXPARAMLOAD(in1=0x16, in2=frame_index, offset=0) → gas_used : uint256 ``` **Behavior:** - Returns the actual gas consumed by the frame at the given index: `frame.gas_limit - leftover_gas` - Exceptional halt if `frame_index >= currently_executing_frame_index` (cannot read current or future frames) This would enable a refund pattern in the DEFAULT frame: ```solidity function postOp(uint256 transferFrameIdx) external { // Verify transfer succeeded uint8 status = FrameTxLib.frameStatus(transferFrameIdx); if (status != 1) revert TransferFrameFailed(); // Compute actual gas cost from all previous frames uint256 totalGasUsed = INTRINSIC_GAS; for (uint256 i = 0; i < FrameTxLib.currentFrameIndex(); i++) { totalGasUsed += FrameTxLib.frameGasUsed(i); // TXPARAMLOAD(0x16, i) } // Estimate own frame's gas and refund excess tokens uint256 estimatedOwnGas = POST_OP_OVERHEAD; uint256 actualTokenCost = (totalGasUsed + estimatedOwnGas) * gasPrice * rate / 1e18; uint256 excess = preChargedAmount - actualTokenCost; if (excess > 0) token.transfer(user, excess); } ``` **The incomplete picture problem:** Even with per-frame gas used, the DEFAULT frame cannot know its **own** gas consumption — it is still executing. The unknown component is bounded and predictable since the sponsor controls their own contract code. ERC-4337 paymasters also estimate their `postOp` gas overhead in practice. However, unlike ERC-4337, there is no protocol-level safety net: ERC-4337's EntryPoint deducts the exact total from the paymaster's ETH deposit *after* `postOp` returns, compensating for estimation errors. In EIP-8141, any estimation error in the DEFAULT frame is absorbed by either the user or sponsor with no protocol backstop. This extension requires no new opcodes — it adds a parameter ID to the existing TXPARAM table. The runtime data (per-frame gas consumption) is already produced during frame execution; it just needs to be retained and exposed to subsequent frames. ## Future Work - **Solady compatibility**: The custom solc compiler's `via_ir = true` mode is incompatible with Solady's `ECDSA.recover()` inline assembly. Currently all implementations use native `ecrecover(hash, v, r, s)` as a workaround. Fixing the compiler to support Solady's assembly patterns would allow using the broader Solady library ecosystem. - **Post-quantum smart account**: Integrate PQ signature verification precompiles (e.g., [EIP-8051](https://eips.ethereum.org/EIPS/eip-8051) ML-DSA, [EIP-7619](https://eips.ethereum.org/EIPS/eip-7619) Falcon512) to build a fully PQ-secure smart account. This would demonstrate EIP-8141's primary motivation, native PQ signature support end to end. - **Mempool validation performance**: The ERC-7562 validation tracer runs full EVM simulation for every VERIFY frame during mempool admission — a fundamentally heavier operation than legacy transaction validation (signature + balance check). Benchmarking the tracer's per-transaction latency and maximum mempool throughput under load would quantify the DoS surface and inform gas limit tuning for VERIFY frames.