Skip to content

Commit dd68caf

Browse files
committed
fix(tests,evm): Reserve bal. & MIP-4 vs initcode execution
Co-Authored-By: Claude opus-4-6
1 parent 74d7269 commit dd68caf

6 files changed

Lines changed: 451 additions & 59 deletions

File tree

src/ethereum/forks/monad_eight/vm/interpreter.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,21 @@ def process_message(message: Message) -> Evm:
325325
if message.depth == 0 and message.tx_env.index_in_block is not None:
326326
for addr in set(state._main_trie._data.keys()):
327327
acc = get_account(state, addr)
328-
if acc.code == b"" or is_valid_delegation(acc.code):
328+
# For creation txs, code hasn't been set yet on the new
329+
# contract (set_code runs after process_message returns). Use
330+
# evm.output which holds the code to be deployed.
331+
if (
332+
isinstance(message.target, Bytes0)
333+
and addr == message.current_target
334+
):
335+
code = evm.output
336+
else:
337+
code = acc.code
338+
339+
# NOTE: this also matches empty code deployments with `Op.STOP`
340+
# or `Op.RETURN(0, 0)`, but this aligns with Monad EVM
341+
# implementation.
342+
if code == b"" or is_valid_delegation(code):
329343
original_balance = get_balance_original(state, addr)
330344
if message.tx_env.origin == addr:
331345
# gas_fees already deducted, need to re-add if sender
@@ -339,9 +353,13 @@ def process_message(message: Message) -> Evm:
339353
threshold = reserve - gas_fees
340354
else:
341355
threshold = RESERVE_BALANCE
342-
is_exception = not is_sender_authority(
343-
state, addr
344-
) and not is_valid_delegation(acc.code)
356+
357+
is_exception = (
358+
message.tx_env.origin == addr
359+
and not is_sender_authority(state, addr)
360+
and not is_valid_delegation(code)
361+
)
362+
345363
if (
346364
acc.balance < original_balance
347365
and acc.balance < threshold

src/ethereum/forks/monad_nine/vm/interpreter.py

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
from ..blocks import Log
3333
from ..fork_types import Address
3434
from ..state import (
35-
State,
3635
account_has_code_or_nonce,
3736
account_has_storage,
3837
begin_transaction,
@@ -47,7 +46,7 @@
4746
rollback_transaction,
4847
set_code,
4948
)
50-
from ..vm import Message, TransactionEnvironment
49+
from ..vm import Message
5150
from ..vm.eoa_delegation import (
5251
get_delegated_code_address,
5352
is_valid_delegation,
@@ -79,18 +78,48 @@
7978
RESERVE_BALANCE = U256(10 * 10**18) # 10 MON
8079

8180

82-
def is_reserve_balance_violated(
83-
state: State,
84-
tx_env: TransactionEnvironment,
85-
) -> bool:
81+
def is_reserve_balance_violated(evm: Evm) -> bool:
8682
"""
8783
Check if any EOA has violated the reserve balance constraint.
8884
8985
Returns True if a violation is detected, False otherwise.
9086
"""
87+
message = evm.message
88+
state = message.block_env.state
89+
tx_env = message.tx_env
90+
91+
# Collect accounts_to_delete from all ancestor frames. accounts_to_delete
92+
# only propagates upward on success (incorporate_child_on_success), so a
93+
# child frame like a precompile call won't see deletions from its parent.
94+
all_accounts_to_delete: Set[Address] = set()
95+
current_evm = evm
96+
while True:
97+
all_accounts_to_delete.update(current_evm.accounts_to_delete)
98+
if current_evm.message.parent_evm is not None:
99+
current_evm = current_evm.message.parent_evm
100+
else:
101+
break
102+
91103
for addr in set(state._main_trie._data.keys()):
104+
# Account SELFDESTRUCTed - skip explicitly.
105+
if addr in all_accounts_to_delete:
106+
continue
107+
92108
acc = get_account(state, addr)
93-
if acc.code == b"" or is_valid_delegation(acc.code):
109+
# For creation txs, code hasn't been set yet on the new contract
110+
# (set_code runs after process_message returns). Use evm.output which
111+
# holds the code to be deployed.
112+
if (
113+
isinstance(message.target, Bytes0)
114+
and addr == message.current_target
115+
):
116+
code = evm.output
117+
else:
118+
code = acc.code
119+
120+
# NOTE: this also matches empty code deployments with `Op.STOP` or
121+
# `Op.RETURN(0, 0)`, but this aligns with Monad EVM implementation.
122+
if code == b"" or is_valid_delegation(code):
94123
original_balance = get_balance_original(state, addr)
95124
if tx_env.origin == addr:
96125
# gas_fees already deducted, need to re-add if sender
@@ -101,9 +130,13 @@ def is_reserve_balance_violated(
101130
threshold = reserve - gas_fees
102131
else:
103132
threshold = RESERVE_BALANCE
104-
is_exception = not is_sender_authority(
105-
state, addr
106-
) and not is_valid_delegation(acc.code)
133+
134+
is_exception = (
135+
message.tx_env.origin == addr
136+
and not is_sender_authority(state, addr)
137+
and not is_valid_delegation(code)
138+
)
139+
107140
if (
108141
acc.balance < original_balance
109142
and acc.balance < threshold
@@ -368,7 +401,7 @@ def process_message(message: Message) -> Evm:
368401
else:
369402
# FIXME: index_in_block is a proxy for not being a system tx
370403
if message.depth == 0 and message.tx_env.index_in_block is not None:
371-
if is_reserve_balance_violated(state, message.tx_env):
404+
if is_reserve_balance_violated(evm):
372405
rollback_transaction(state, transient_storage)
373406
evm.error = RevertOnReserveBalance()
374407
return evm

src/ethereum/forks/monad_nine/vm/precompiled_contracts/reserve_balance.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,6 @@ def reserve_balance(evm: Evm) -> None:
8686
raise RevertInMonadPrecompile
8787

8888
# OPERATION
89-
violation = is_reserve_balance_violated(
90-
evm.message.block_env.state,
91-
evm.message.tx_env,
92-
)
89+
violation = is_reserve_balance_violated(evm)
9390
# Return bool encoded as uint256 (32 bytes)
9491
evm.output = U256(1 if violation else 0).to_be_bytes32()

tests/cancun/eip6780_selfdestruct/test_selfdestruct.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
Transaction,
2727
compute_create_address,
2828
)
29-
from execution_testing.forks import Cancun
29+
from execution_testing.forks import MONAD_EIGHT, Cancun
3030

3131
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md"
3232
REFERENCE_SPEC_VERSION = "1b6a0e94cc47e859b9866e570391cf37dc55059a"
@@ -378,6 +378,7 @@ def test_self_destructing_initcode(
378378
# in the same tx
379379
selfdestruct_contract_initial_balance: int,
380380
fork_extra_gas: int,
381+
fork: Fork,
381382
) -> None:
382383
"""
383384
Test that a contract can self-destruct in its initcode.
@@ -481,15 +482,26 @@ def test_self_destructing_initcode(
481482

482483
entry_code_address = tx.created_contract
483484

484-
post: Dict[Address, Account] = {
485-
entry_code_address: Account(
486-
storage=entry_code_storage,
487-
),
488-
selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore
489-
sendall_recipient_addresses[0]: Account(
490-
balance=sendall_amount, storage={0: 1}
491-
),
492-
}
485+
reverted = (
486+
fork == MONAD_EIGHT and selfdestruct_contract_initial_balance > 0
487+
)
488+
489+
if reverted:
490+
post: Dict[Address, Account] = {
491+
selfdestruct_contract_address: Account(
492+
balance=selfdestruct_contract_initial_balance,
493+
),
494+
}
495+
else:
496+
post = {
497+
entry_code_address: Account(
498+
storage=entry_code_storage,
499+
),
500+
selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore
501+
sendall_recipient_addresses[0]: Account(
502+
balance=sendall_amount, storage={0: 1}
503+
),
504+
}
493505

494506
state_test(pre=pre, post=post, tx=tx)
495507

@@ -509,6 +521,7 @@ def test_self_destructing_initcode_create_tx(
509521
sendall_recipient_addresses: List[Address],
510522
selfdestruct_contract_initial_balance: int,
511523
fork_extra_gas: int,
524+
fork: Fork,
512525
) -> None:
513526
"""
514527
Use a Create Transaction to execute a self-destructing initcode.
@@ -537,12 +550,23 @@ def test_self_destructing_initcode_create_tx(
537550
# contract
538551
sendall_amount = selfdestruct_contract_initial_balance + tx_value
539552

540-
post: Dict[Address, Account] = {
541-
selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore
542-
sendall_recipient_addresses[0]: Account(
543-
balance=sendall_amount, storage={0: 1}
544-
),
545-
}
553+
reverted = (
554+
fork == MONAD_EIGHT and selfdestruct_contract_initial_balance > 0
555+
)
556+
557+
if reverted:
558+
post: Dict[Address, Account] = {
559+
selfdestruct_contract_address: Account(
560+
balance=selfdestruct_contract_initial_balance,
561+
),
562+
}
563+
else:
564+
post = {
565+
selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore
566+
sendall_recipient_addresses[0]: Account(
567+
balance=sendall_amount, storage={0: 1}
568+
),
569+
}
546570

547571
state_test(pre=pre, post=post, tx=tx)
548572

0 commit comments

Comments
 (0)