Repository navigation
feat(fork-types): add ExecutionGas and StateGas NewType wrappers to amsterdam EVM - #3121
Conversation
546a7e6 to
356a1a8
Compare
|
@gurukamath I updated this PR with the Should we add this and do we need to backport the execution gas change? |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## forks/amsterdam #3121 +/- ##
================================================
Coverage 93.49% 93.50%
================================================
Files 624 624
Lines 37056 37070 +14
Branches 3394 3394
================================================
+ Hits 34647 34661 +14
Misses 1653 1653
Partials 756 756
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
gurukamath
left a comment
There was a problem hiding this comment.
Some minor nits. I think the branch also needs a rebase to avoid merge conflicts
| gas_price: Uint | ||
| gas: Uint | ||
| state_gas_reservoir: Uint | ||
| gas: ExecutionGas |
There was a problem hiding this comment.
Should we change the types in BlockOutput as well?
| gas_meter.gas_left += child_meter.gas_left | ||
| gas_meter.state_gas_left += child_meter.state_gas_left | ||
| gas_meter.state_gas_spilled += child_meter.state_gas_spilled | ||
| gas_meter.gas_left = ExecutionGas( |
There was a problem hiding this comment.
This should work in its current form since they are all the same type, right? Also applies to some of the other gas helper functions I think
| if gas_meter.gas_left < amount: | ||
| raise OutOfGasError | ||
| gas_meter.gas_left -= amount | ||
| gas_meter.gas_left = ExecutionGas(gas_meter.gas_left - amount) |
There was a problem hiding this comment.
I feel like the types should show up in the function signatures instead of a later cast in the body. Should amount be ExecutionGas? Applies for the other gas functions as well
There was a problem hiding this comment.
GasCosts.CALL_VALUE and Uint(0) have different types here
356a1a8 to
3d44ead
Compare
Upstream ethereum#3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly.
…msterdam EVM (ethereum#3121) Co-authored-by: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com>
Upstream ethereum#3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly.
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
…3047) * feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream ethereum#3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
…3047) * feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream ethereum#3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
…3047) * feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream ethereum#3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
…3047) * feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream ethereum#3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
…3047) * feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream ethereum#3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
…3047) * feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream ethereum#3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
* feat(specs): EIP-8141 transaction types * WiP * feat(specs): EIP-8141 frame transaction processing 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> * feat(spec-tools): adapt t8n for FrameTransaction Co-authored-by: lightclient <lightclient@protonmail.com> * feat(test-types): Add EIP-8141 support to testing framework Co-authored-by: lightclient <lightclient@protonmail.com> * feat(tests): EIP-8141 frame transaction tests Co-authored-by: lightclient <lightclient@protonmail.com> * fix(bogota): correct SIGPARAM copy operand order 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. * Restore minimal comment on SIGPARAM copy form * fix: warm the sender for frame transactions 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. * feat(spec): add EXPRIY_VERIFIER contract * feat(tests): add introspection tests test: cover the EIP-8141 introspection instructions * feat(tests): add more introspection tests * feat(tests): add frame transaction warmth tests * fix(tests): restore EIP-8141 reference spec pin * fix(spec): adapt frame transaction gas to ExecutionGas/StateGas newtypes Upstream #3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly. * feat(tests): add frame transaction validity tests * feat(tests): more frame transaction validity tests * fix(tests): fix broken egacy tests for frames * feat(tests): add block access list cases for frame transactions * 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> * feat(tests): additional gas helpers for frame transactions * feat(tests): add frame target resolution test cases * 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> * feat(tests): create transaction tests from validity checks * fix(specs,tests): add max gas checks and tests plus some other fixes suggested in spencers review * feat(tests): cover the frame-entry charge a target that is not alive adds This covers the third charge `create_evm_from_frame` levies against a frame's own gas before its EVM is built: the `NEW_ACCOUNT` state gas a client levies when the frame's value transfer revives a target that is not alive. `test_frame_target_entry_charge` covers the target's own access and `test_delegated_target_entry_charge` the delegate's; this covers the remaining one. `test_dead_target_entry_charge` covers the case where a client enters a `SENDER` frame moving value to an account that is not alive. The target has no code, so the frame runs empty code and the receipt's `gas_used` is the entry charge alone. `NEW_ACCOUNT` is 120 state bytes, 183_600 gas, which is more than a frame usually declares, so the affordable arm is only half the behavior. The second arm gives the frame one gas less than the charge and covers the case where a client cannot enter the frame at all: it forfeits its whole gas limit, the target stays dead, and no transfer log is emitted. The case pins per-frame `gas_used` rather than the transaction total, because the EIP-7623 calldata floor can dominate a small frame transaction's total and hold it constant while a frame's own gas moves. `frame_entry_gas_calculator` already takes `sends_value_to_dead_account` and the test derives its expectations from it. Filled against the reference implementation; both arms go red if the charge is not levied. It came up while running a devnet. Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com> --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: Daniil Ankushin <ankushin.daniil42@gmail.com> Co-authored-by: Marc <Marchhill@users.noreply.github.com> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: ilitteri <ivan.litteri@lambdaclass.com>
Description
Applies the
ExecutionGasandStateGasNewTypewrappers (introduced infork_types.pyby #3037) to all EVM gas fields and constants across theamsterdam fork, preventing accidental mixing of state gas with execution gas
at the type level.
Changes:
GasCostsconstants typed asExecutionGas(blob-fee, byte-count andblock-limit constants stay
Uint);StateGasCostsalready typed asStateGasGasMeterfields typed:gas_left,state_gas_left,state_gas_baseline,state_gas_spilled,state_gas_committed_spillMessage,TransactionEnvironment,MessageCallOutput,GenericCall,MessageCallGasandExtendMemorygas fields typedEvmGasAllocationand the per-dimensionTransactionGasSettlementfieldstyped
charge_state_gas, spillcommit/restore,
withhold_create_gas,drain_state_gas_reservoir,restore_child_gas,tx_state_gas_used),max_message_call_gas,calculate_delegation_costandsettle_transaction_gasRebased onto
forks/amsterdamafter the regular→execution gas rename(#3238, #3263) and the
GasMeterrefactor, so the wrapper is now namedExecutionGasand the typing covers the meter and settlement helpers.Related Issues or PRs
Closes #3015. Follows on from #3037.
Checklist
just static<type>(<area>): <title>, where<type>and<area>come from an appropriateC-<type>, respectivelyA-<area>, label. The title should match the target squash commit message.Cute Animal Picture