Skip to content

Update EIP-8272: specify RECENT_ROOT_CODE - #12131

Open
AnkushinDaniil wants to merge 2 commits into
ethereum:masterfrom
AnkushinDaniil:daniil/eip8272-recent-root-code
Open

Update EIP-8272: specify RECENT_ROOT_CODE#12131
AnkushinDaniil wants to merge 2 commits into
ethereum:masterfrom
AnkushinDaniil:daniil/eip8272-recent-root-code

Conversation

@AnkushinDaniil

Copy link
Copy Markdown
Contributor

RECENT_ROOT_CODE is currently TBD, so the recent root contract has a fully specified behaviour and no code that implements it. A client that installs the predeploy today installs an empty account, every write is a plain value transfer that commits nothing, and every reference then fails its check. That makes the whole recent root path untestable end to end, so this fills the constant in.

The code is assembled directly from the "Recent root contract" section rather than compiled, so the mapping between the two is line by line and reviewable. It is 144 bytes.

Two prohibitions in that section are met without an explicit check, which the text now says out loud. In static context the sstore fails on its own. Under DELEGATECALL or CALLCODE the sstore writes to the calling account, so recent root storage is untouched either way.

The current slot comes from SLOTNUM, which this EIP already requires through EIP-7843, so the contract never needs to know the slot duration.

I ran it on a devnet before proposing it. A write from an EOA produced exactly the storage_key and entry_hash the section prescribes:

expected key  0x977593f0ea0ed47883bc77610e2cdda7a41763ad0723141b26b84b31228000bb
expected hash 0xf31b67e03c4496b8b2a20dbd0765f97af657624133b5168f8aec986f58a261aa
on chain      0xf31b67e03c4496b8b2a20dbd0765f97af657624133b5168f8aec986f58a261aa

A write costs 127244 gas. All three revert conditions hold: calldata shorter than 64 bytes, longer than 64 bytes, and a nonzero call value.

Two things I would rather the authors decide than assume:

The modulus is compiled as a mask, push2 0x1fff, which is correct only while RECENT_ROOT_LENGTH is 8192. If that constant is still open, a mod costs 3 more gas and survives any future value.

The address is fixed at 0x…8272 and the code is installed at activation, so there is no deployment transaction and no synthetic sender the way EIP-2935 and EIP-4788 have one. That seemed right for this EIP, but it is a departure from those two and worth confirming.

@AnkushinDaniil
AnkushinDaniil requested a review from eth-bot as a code owner August 7, 2026 19:05
@github-actions github-actions Bot added c-update Modifies an existing proposal s-draft This EIP is a Draft t-core labels Aug 7, 2026
@eth-bot

eth-bot commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

File EIPS/eip-8272.md

Requires 1 more review from Authors: @nerolation, @soispoke, @vbuterin

@eth-bot eth-bot added the a-review Waiting on author to review label Aug 7, 2026
AnkushinDaniil added a commit to AnkushinDaniil/ethrex that referenced this pull request Aug 8, 2026
… a native write

The spec left RECENT_ROOT_CODE TBD, so the 64-byte salt-root write was
handled as a VM-level special case on four paths. ethereum/EIPs#12131
specifies the bytecode, so the predeploy carries it and the write is
ordinary EVM execution: the entry a caller commits and the gas it pays
are both what the code does, and the four interception points go away.

Without the code the write bypassed the EIP-8037 state-gas charge for
the created slot, which forked the family devnet against a client that
executes the predeploy.
@ilitteri

ilitteri commented Aug 8, 2026

Copy link
Copy Markdown

Thanks for filling in RECENT_ROOT_CODE. I reviewed the bytecode independently and could not find a correctness issue in the core write path.

The hex matches the assembly, the 0x10 jump destination is valid, and the overlapping MSTORE layout produces exactly the specified preimages:

  • source_id = keccak256(msg.sender[20] || salt[32])
  • entry_hash = keccak256(ENTRY_DOMAIN || source_id || uint64_be(S) || root)
  • storage_key = keccak256(STORAGE_DOMAIN || source_id || uint64_be(S mod 8192))

The embedded domain constants also match keccak256("RECENT_ROOT_ENTRY") and keccak256("RECENT_ROOT_STORAGE"), and using S & 0x1fff is correct while RECENT_ROOT_LENGTH = 8192.

A few points I think are worth clarifying before merge:

  1. Delegate-style calls can still write foreign storage and succeed

    The text correctly says that DELEGATECALL and CALLCODE do not write recent-root storage. However, the bytecode does not revert in those contexts: the final SSTORE writes the derived entry into the calling account's storage and then returns successfully.

    So the precise guarantee is:

    only execution whose storage context is RECENT_ROOT_ADDRESS writes recent-root storage

    not:

    non-direct execution performs no storage write at all

    I think it would be worth making that explicit so client implementers do not assume these calls fail.

  2. Please cover the EIP-7702 delegation case

    The same foreign-storage behavior occurs if an EIP-7702 delegated account points to RECENT_ROOT_ADDRESS: calling the delegated account executes this code in the delegated account's storage context.

    Since the EIP already discusses 7702 and EIP-8141 includes 7702 semantics, I would explicitly mention this case next to DELEGATECALL/CALLCODE and add a test for it.

  3. Activation should explicitly require EIP-7843

    The header lists EIP-7843 as a requirement and the bytecode uses SLOTNUM, but the Activation section currently says only:

    This EIP MUST activate at or after EIP-8141.

    I suggest changing this to:

    This EIP MUST activate at or after EIP-8141 and EIP-7843.

    That avoids a broken chain configuration where RECENT_ROOT_CODE is installed but 0x4b is not active.

  4. Static-call gas behavior should be pinned by tests

    For malformed calldata or nonzero value, the code exits through REVERT. In a static context with valid-shaped calldata, it reaches SSTORE and exceptional-halts, consuming the remaining gas in the subcontext.

    That is consistent with the current wording (“revert” for malformed input versus “fail” for static context), but the difference should be captured in execution tests because it affects exact gas usage.

  5. Test matrix

    The PR description gives one successful end-to-end vector. I think this needs a broader test matrix before clients can rely on it for interop:

    • calldata lengths 0, 63, 64, and 65
    • zero and nonzero call value
    • ordinary CALL
    • top-level transaction to the predeploy
    • STATICCALL
    • DELEGATECALL
    • CALLCODE
    • EIP-7702 delegated execution
    • cold fresh write, cold overwrite, and warm overwrite
    • writes followed by an enclosing revert
    • repeated writes by the same (caller, salt) in one slot
    • slots 0, 1, 8191, 8192, 8195, and 2^64 - 1
    • reference ages 0, 1, 8191, and 8192
  6. Gas claim in the PR description

    The PR description says the write costs 127244 gas. Since the code follows ordinary EVM accounting, the actual cost depends on call/frame overhead, memory expansion, Keccak work, and the dynamic SSTORE state transition. Could you break that number down or clarify whether it is total transaction gas rather than runtime execution gas?

  7. Related mempool issue

    Now that this EIP makes SLOTNUM load-bearing for frame transactions, I think the EIP-8141 validation-prefix rules should ban SLOTNUM, as proposed in Update EIP-8141: ban SLOTNUM during validation-prefix execution #12066. Otherwise validation code can branch on the slot and pass mempool simulation but fail at inclusion.

None of these are objections to the bytecode itself. The core implementation looks sound; my requests are mainly about making the edge semantics and activation requirements explicit enough for independent client implementations.

@nerolation

Copy link
Copy Markdown
Contributor

I'd propose to use fjl/geas instead. We did that for other EIPs that shipped before.

AnkushinDaniil added a commit to AnkushinDaniil/ethrex that referenced this pull request Aug 8, 2026
… a native write

The spec left RECENT_ROOT_CODE TBD, so the 64-byte salt-root write was
handled as a VM-level special case on four paths. ethereum/EIPs#12131
specifies the bytecode, so the predeploy carries it and the write is
ordinary EVM execution: the entry a caller commits and the gas it pays
are both what the code does, and the four interception points go away.

Without the code the write bypassed the EIP-8037 state-gas charge for
the created slot, which forked the family devnet against a client that
executes the predeploy.
@soispoke

Copy link
Copy Markdown
Contributor

thanks @AnkushinDaniil @ilitteri! I agree with @nerolation that the runtime should be written in fjl/geas and maintained in ethereum/sys-asm with generated bytecode and tests

my suggestion would be to keep RECENT_ROOT_LENGTH = 8192, and then no strong preference between the modulo operation or using the 0x1fff mask. Feels like the trade off is simplicity or gas efficiency (saves 2 gas on writes) but forced to rely on a power of two mask

and then I think a synthetic deployment transaction is better, it would follow existing EIP precedent and avoid a special fork state change

@AnkushinDaniil

Copy link
Copy Markdown
Contributor Author

Thanks both. Agreed on all three.

Moved the runtime to geas and opened it against sys-asm: ethereum/sys-asm#53. It assembles byte-identical to the 144 bytes currently in the EIP, with a ctor in the same shape as the other predeploys there and a Foundry test for the guards. Once that lands I will replace the hex block here with a reference to the generated bytecode.

Keeping RECENT_ROOT_LENGTH = 8192 and the 0x1fff mask, since the length is already a power of two and the mask saves the two gas.

On the synthetic deployment transaction: I will rewrite the Activation section to install the code with a Nick-method deployment rather than a fork state change, following the 4788 precedent. That makes the address deployment-derived instead of the current 0x...8272, so I will follow up with the deployment transaction and the resulting address.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a-review Waiting on author to review c-update Modifies an existing proposal s-draft This EIP is a Draft t-core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants