Skip to content

Frame Transactions (EIP-8141) - #3047

Open
SamWilsn wants to merge 21 commits into
ethereum:eips/amsterdam/eip-8141from
SamWilsn:eips/amsterdam/eip-8141
Open

Frame Transactions (EIP-8141)#3047
SamWilsn wants to merge 21 commits into
ethereum:eips/amsterdam/eip-8141from
SamWilsn:eips/amsterdam/eip-8141

Conversation

@SamWilsn

Copy link
Copy Markdown
Contributor

🗒️ Description

My work so far on frame transactions (EIP-8141)

Cute Animal Picture

IMG_20260624_154128_797

@SamWilsn SamWilsn mentioned this pull request Jul 7, 2026
6 tasks
@gurukamath gurukamath self-assigned this Jul 21, 2026
@gurukamath
gurukamath force-pushed the eips/amsterdam/eip-8141 branch from 362b750 to 1402c53 Compare July 21, 2026 13:02
@gurukamath
gurukamath force-pushed the eips/amsterdam/eip-8141 branch from 1402c53 to 2f8aa48 Compare July 31, 2026 08:11
@gurukamath
gurukamath changed the base branch from forks/amsterdam to eips/amsterdam/eip-8141 July 31, 2026 08:12
@gurukamath

Copy link
Copy Markdown
Contributor

Note to reviewers: The first commit of this PR ccd0c593af5 is a spec refactor that should be upstreamed independent of EIP-8141. This is exactly what #3192 does. The actual Frame Transaction implementation is in the second commit onwards.

I have built the frame transaction implementation on top of the re-factor since the changes introduced in the refactor make it much nicer to implement frames. Once PR#3192 gets approved and merged, I will rebase this PR branch.

@gurukamath
gurukamath marked this pull request as ready for review July 31, 2026 08:18
@gurukamath
gurukamath requested a review from lightclient July 31, 2026 08:18
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.17%. Comparing base (343274c) to head (cd654d9).

Additional details and impacted files
@@                     Coverage Diff                     @@
##           eips/amsterdam/eip-8141    #3047      +/-   ##
===========================================================
- Coverage                    93.53%   93.17%   -0.37%     
===========================================================
  Files                          624      576      -48     
  Lines                        37070    33246    -3824     
  Branches                      3394     2975     -419     
===========================================================
- Hits                         34675    30976    -3699     
+ Misses                        1645     1578      -67     
+ Partials                       750      692      -58     
Flag Coverage Δ
unittests 93.17% <ø> (-0.37%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gurukamath
gurukamath force-pushed the eips/amsterdam/eip-8141 branch from 2f8aa48 to aef48b6 Compare July 31, 2026 11:13
@gurukamath gurukamath removed their assignment Jul 31, 2026
@gurukamath
gurukamath requested a review from marioevz July 31, 2026 19:34
@gurukamath
gurukamath force-pushed the eips/amsterdam/eip-8141 branch 2 times, most recently from fb05113 to 7f4ee85 Compare August 4, 2026 15:34
VERSIONED_HASH_VERSION_KZG,
)

if U256(tx.nonce) >= U256(U64.MAX_VALUE):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary cast.

@Marchhill

Copy link
Copy Markdown
Contributor

Hey I made a couple of fixes, opened PRs:
SamWilsn#9
SamWilsn#10

@gurukamath
gurukamath force-pushed the eips/amsterdam/eip-8141 branch from cb6d6cf to 94b7234 Compare August 6, 2026 12:25
@gurukamath

Copy link
Copy Markdown
Contributor

Hey I made a couple of fixes, opened PRs: SamWilsn#9 SamWilsn#10

Thanks @Marchhill
Merged!

Comment thread src/ethereum/forks/amsterdam/transactions/frame_transaction.py
SamWilsn and others added 9 commits August 10, 2026 14:54
Implement the type-6 frame transaction flow on the amsterdam fork,
separated from the regular flow from admission onwards:

- static validation, signature verification, intrinsic gas, and the
  two gas anchors (standard_gas_limit, max_gas) in
  transactions/frame_transaction.py
- consensus receipt types (FrameReceipt, FrameTransactionReceipt)
  and the 0x06 receipt codec arms in blocks.py
- TransactionEnvironment split into top_level_context/frame_context
  in vm/__init__.py
- admission through a dedicated check_frame_transaction in
  frame_processing.py; process_transaction dispatches frame
  transactions after the chain-id check
- frames execute as independent top-level calls in
  vm/frame_interpreter.py: per-frame gas meters, atomic batches,
  cross-frame warm journal, default verify code, approval rollback
- the six frame opcodes (TXPARAM, FRAMEDATALOAD, FRAMEDATACOPY,
  FRAMEPARAM, SIGPARAM, APPROVE) in vm/instructions/frame.py
- settlement anchored on the standard gas limit with the calldata
  floor binding through max_gas; the payer refund is
  max_cost - charged_fee; the frame transaction receipt reports
  status, gas, and logs per frame

Co-authored-by: lightclient <lightclient@protonmail.com>
Co-authored-by: lightclient <lightclient@protonmail.com>
Co-authored-by: lightclient <lightclient@protonmail.com>
Co-authored-by: lightclient <lightclient@protonmail.com>
EIP-8141 aligned the SIGPARAM (0xb4) copy operation's stack order with
CALLDATACOPY (ethereum/EIPs 4a9ad32c): after signatureIndex and param the
operands are memOffset, dataOffset, length. The implementation still popped
them in the old order (length, dataOffset, memOffset) — the reverse of its
own FRAMEDATACOPY. Swap to match the live spec.
Frame transaction processing initializes the shared, once-per-tx warm
address set with the sender so its first EVM access is charged warm
(100) rather than cold (2600). The sender is loaded unconditionally for
the nonce check and, like a regular transaction's origin, must start
warm per EIP-2929 and EIP-3651.
@Marchhill

Copy link
Copy Markdown
Contributor

Added some test cases: SamWilsn#12

Marchhill and others added 2 commits August 12, 2026 08:39
* feat(tests): frame-transaction block access list cases

Add EIP-8141 frame-transaction cases that pin the consensus-visible
EIP-7928 block access list across frame rollback paths: an atomic-batch
unroll and a frame revert drop the discarded storage write and re-file
the slot as a bare access; a skipped atomic-batch frame stays absent
from the BAL; and a sponsored transaction attributes the payer's fee
and the sender's nonce to distinct accounts. These paths are exercised
only as state tests today, so the BAL is unpinned for them.

* refactor(tests): tighten frame BAL cases and reuse module helpers

Build the frames with the module's `verify_frame`/`sender_frame`
helpers instead of hand-rolled `Frame` literals, matching the rest of
the suite.

Tighten the assertions so each case pins what it claims: the sponsored
transaction now asserts the sender has no balance change and the payer
no nonce bump, so a spurious BAL entry cannot pass on a correct post
state, and the atomic-batch cases assert the sender's nonce bump is
recorded whether or not the batch unrolls.

Derive the payer's post balance from the charged gas and fee per gas
rather than hardcoding it, and pin both on the transaction, so a gas
change surfaces as a gas mismatch instead of an opaque balance.

---------

Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>
@gurukamath

Copy link
Copy Markdown
Contributor

Added some test cases: SamWilsn#12

@Marchhill Merged. I have also added the test cases to the list here
We are maintaining this list so everyone working on frames can dump their test case ideas here, if needed.

* feat(tests): frame target resolution cases

Cover the three ways a frame's resolved target carries the empty code
hash: a precompile under each non-VERIFY mode and under VERIFY, and an
EIP-7702 authority whose designation is resolved at frame entry or
points at a precompile.

* feat(tests): discriminate the VERIFY target resolution split

Move the approvals of the precompile-target `VERIFY` case onto the
first frame and leave the frame under test approving nothing. With the
approvals on the frame under test, dispatching the precompile leaves
the transaction with no payment approver, so it is rejected either way
and the case rests on matching the exact rejection reason; with them
moved, dispatching makes the transaction valid.

Add a `VERIFY` frame whose target designates a precompile: its
resolved code is empty, as for the codeless target that does route to
the default code, but the target holds a designation and so must
follow it instead.

* refactor(tests): pair the warming frame with its receipt

The warming frame, its expected receipt and the designated address's
access charge each branched on `warm_delegate` separately, the receipt
through a comprehension over the frame list. Decide all three in one
place so the frame and its receipt cannot drift apart.

* feat(tests): frame gas that cannot afford the designation access

The designated account is read only once its access is paid for, so a
frame that cannot afford it leaves the account out of the block access
list as well as unexecuted.

* refactor(tests): use the frame entry gas calculator

Account the frame entry charges with `fork.frame_entry_gas_calculator()`
rather than summing `gas_costs` members, so the target and designation
access charges come from one place.

Pin the sender's nonce as unchanged in the rejected precompile-target
case, and move the unaffordable-designation case to the block access
list suite: the receipts cannot tell a resolution failure from a
failure inside the resolved code, since either forfeits the whole frame
gas limit, so assert the designated account absent from the BAL and the
target present as a bare access. Its frame gas is the largest that
still cannot afford the designation's access.

---------

Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>

@spencer-tb spencer-tb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small catch! Feel free to merge, will review in more detail during the week!

with:
python-version: "pypy3.11"
- uses: ./.github/actions/setup-env-pypy
# Stop short of Amsterdam: this branch's amsterdam spec module

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make these "TODO:"'s?

Comment on lines +592 to +595
if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
raise PriorityFeeGreaterThanMaxFeeError(
"priority fee greater than max fee"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
raise PriorityFeeGreaterThanMaxFeeError(
"priority fee greater than max fee"
)
if tx.max_fee_per_gas > Uint(U256.MAX_VALUE):
raise InvalidFrameError("max fee per gas exceeds 2**256 - 1")
if tx.max_priority_fee_per_gas > Uint(U256.MAX_VALUE):
raise InvalidFrameError("max priority fee per gas exceeds 2**256 - 1")
if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
raise PriorityFeeGreaterThanMaxFeeError(
"priority fee greater than max fee"
)

I think we should be adding this validation as per the EIPs constraints: max_fee_per_gas < 2**256 / max_priority_fee_per_gas < 2**256.

Without this, as frame transactions have no upfront balance >= gas * max_fee check to make oversized values unincludable, these txs will execute and raise an uncaught OverflowError from U256(tx.max_fee_per_gas) instead of the transaction being rejected, I think

We should add basic tests for this too!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants