Skip to content

fix(tests,evm): Reserve bal. & MIP-4 vs initcode execution#19

Merged
pdobacz merged 1 commit intoforks/monad_ninefrom
create-selfd-update
Mar 20, 2026
Merged

fix(tests,evm): Reserve bal. & MIP-4 vs initcode execution#19
pdobacz merged 1 commit intoforks/monad_ninefrom
create-selfd-update

Conversation

@pdobacz
Copy link
Collaborator

@pdobacz pdobacz commented Mar 18, 2026

Adjusts tests to category-labs/monad#2037 + other aspects of MIP-4 and reserve balance rules (referenced below).
Expands tests to cover edge cases

@pdobacz pdobacz requested review from QEDK and mijovic as code owners March 18, 2026 17:35
@greptile-apps
Copy link

greptile-apps bot commented Mar 18, 2026

Greptile Summary

This PR fixes two related issues in the Monad EVM reserve-balance enforcement: (1) contracts that self-destruct during initcode should be exempted from the end-of-tx reserve-balance check because their balance legitimately drops to zero, and (2) for creation transactions the correct code to test against is evm.output (the code that will be deployed), not acc.code which is still b"" at check time. A new accounts_to_delete ancestor-traversal is added to is_reserve_balance_violated so that a selfdestructed account is skipped regardless of whether the check fires from the end-of-tx path or from the MIP-4 dippedIntoReserve() precompile mid-execution. The is_exception condition is also tightened: it previously fired for any non-sender-authority, non-delegation account; it now additionally requires the account to be the transaction origin, so only the sender gets the gas-adjusted threshold exception. Tests are expanded with a (selfdestruct, deploy_code) parametrisation to separately cover empty-code deployments (which still fall under reserve-balance) vs Op.STOP-code deployments (which do not), and two previously-skipped MIP-4 precompile tests are implemented.

Key changes:

  • is_reserve_balance_violated now takes evm and traverses all ancestor frames to collect accounts_to_delete, correctly skipping selfdestructed accounts in both the end-of-tx enforcement path and the mid-execution precompile path
  • Creation-tx code is read from evm.output at check time (not acc.code = b"") so that the deployed code is used for the reserve-balance gate
  • is_exception now requires origin == addr, tightening the sender-exception to only the actual transaction sender
  • tests/cancun selfdestruct tests gain MONAD_EIGHT-aware reverted post-state handling
  • tests/monad_eight creation-tx tests gain edge-case coverage for empty-code vs Op.STOP deployments and fork-specific selfdestruct revert paths
  • tests/monad_nine adds test_contract_unrestricted_within_initcode and test_unrestricted_in_creation_tx_initcode verifying dippedIntoReserve() reports violations during initcode correctly

Confidence Score: 4/5

  • PR is safe to merge with one logic comment worth verifying; the core implementation changes are correct and well-tested.
  • The implementation changes are logically sound: accounts_to_delete traversal correctly handles all invocation contexts, evm.output fallback for creation-tx code is correct, and is_exception tightening matches confirmed intended behavior. Test assertions for balance amounts, violation flags, and post-state were verified by tracing execution paths manually. One filed comment on test_contract_unrestricted_within_initcode targets lines that already use balance - value for selfdestruct_target (the code is correct), so please disregard that suggestion. Score is 4 rather than 5 due to the complexity of the multi-context accounts_to_delete traversal and the fork-conditional test logic which merits careful human review.
  • The new ancestor-traversal loop in src/ethereum/forks/monad_nine/vm/interpreter.py (lines 94-101) deserves attention: it walks parent_evm links from the current frame to the root. This is correct for the precompile-call context but for the end-of-tx invocation from process_message the root evm has no parent and only its own accounts_to_delete (already accumulated from children) is needed — the loop still terminates correctly in one iteration but could be simplified with a comment.

Important Files Changed

Filename Overview
src/ethereum/forks/monad_nine/vm/interpreter.py Refactors is_reserve_balance_violated to accept evm instead of state, tx_env, adds ancestor accounts_to_delete traversal to correctly skip selfdestructed accounts, and adds creation-tx code detection via evm.output. The is_exception condition now requires origin == addr, restricting exceptions to the tx sender. Logic is sound; the call-chain traversal correctly handles both end-of-tx and mid-execution precompile invocations.
src/ethereum/forks/monad_eight/vm/interpreter.py Ports the same creation-tx code detection (evm.output fallback) and tightened is_exception logic from monad_nine. Note: process_create_message can call rollback_transaction twice when a reserve-balance violation fires at depth 0 (once inside process_message, once in the else branch of process_create_message), but this is pre-existing behaviour unchanged by this PR.
src/ethereum/forks/monad_nine/vm/precompiled_contracts/reserve_balance.py Minimal change: passes evm directly to is_reserve_balance_violated instead of state, tx_env, consistent with the refactored signature. No logic changes.
tests/cancun/eip6780_selfdestruct/test_selfdestruct.py Adds fork fixture and conditional reverted post-state for MONAD_EIGHT when a pre-funded contract selfdestructs during initcode. Correct: in MONAD_EIGHT, the selfdestructed contract ends up with empty code and zero balance, triggering reserve-balance revert. In MONAD_NINE, the account is in accounts_to_delete and is skipped. Post-state only asserts the pre-funded address balance on revert; it does not assert sendall_recipient_addresses[0] is empty, which is minor but acceptable.
tests/monad_eight/reserve_balance/test_transfers.py Expands (selfdestruct, deploy_code) parametrisation across four tests to cover empty-bytecode deployments, adds fork-aware reverted conditions including the new empty-code violation path (len(deploy_code) == 0), and replaces hard-coded Op.STOP in expected post-state code fields with the parametrised deploy_code. Assertions are correct and the use of len(deploy_code) == 0 addresses the previous review concern about Bytecode() equality.
tests/monad_nine/mip4_checkreservebalance/test_transfers.py Adds two previously-skipped tests (test_contract_unrestricted_within_initcode, test_unrestricted_in_creation_tx_initcode). Both tests check dippedIntoReserve() once during and optionally once after initcode execution, with the full (selfdestruct, deploy_code) parametrisation. expected_violation_in_initcode and expected_violation_after_create logic is correct, and the factory's refill_call prevents end-of-tx revert from masking the in-flight detection results.

Sequence Diagram

sequenceDiagram
    participant TX as Transaction
    participant PM as process_message (depth=0)
    participant PM2 as process_message (depth>0 / initcode)
    participant Precompile as reserve_balance precompile
    participant IRV as is_reserve_balance_violated(evm)

    Note over TX,IRV: End-of-tx check (MONAD_EIGHT / MONAD_NINE)
    TX->>PM: execute top-level call
    PM->>PM2: nested CALL / CREATE (accounts_to_delete propagated up on success)
    PM2-->>PM: return (accounts_to_delete merged via incorporate_child_on_success)
    PM->>IRV: is_reserve_balance_violated(root_evm)
    Note right of IRV: Walk root_evm.accounts_to_delete<br>(single frame, no ancestor traversal needed)
    IRV->>IRV: skip addr if in accounts_to_delete
    IRV->>IRV: for creation tx: code = evm.output<br>(not yet set via set_code)
    IRV->>IRV: is_exception = (origin==addr AND<br>not is_sender_authority AND<br>not is_valid_delegation)
    IRV-->>PM: True → rollback / False → commit

    Note over TX,IRV: Mid-execution check via dippedIntoReserve() precompile (MONAD_NINE)
    PM2->>Precompile: CALL dippedIntoReserve()
    Precompile->>IRV: is_reserve_balance_violated(precompile_evm)
    Note right of IRV: Traverse ancestors collecting<br>accounts_to_delete from all frames
    IRV->>IRV: skip addr if in any ancestor's accounts_to_delete
    IRV->>IRV: code = acc.code (not Bytes0 target in mid-exec)
    IRV-->>Precompile: 0 or 1 (no revert, just detection)
    Precompile-->>PM2: return uint256(0 or 1)
Loading

Last reviewed commit: "fix(tests,evm): Rese..."

@pdobacz pdobacz force-pushed the create-selfd-update branch from dd68caf to f3092ea Compare March 19, 2026 09:50
@pdobacz pdobacz force-pushed the create-selfd-update branch from f3092ea to 8885d5d Compare March 19, 2026 10:41
@pdobacz
Copy link
Collaborator Author

pdobacz commented Mar 19, 2026

@greptileai re review

@pdobacz pdobacz merged commit 5e31d7c into forks/monad_nine Mar 20, 2026
5 checks passed
@pdobacz pdobacz deleted the create-selfd-update branch March 20, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants