Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions src/ethereum/forks/monad_eight/vm/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,21 @@ def process_message(message: Message) -> Evm:
if message.depth == 0 and message.tx_env.index_in_block is not None:
for addr in set(state._main_trie._data.keys()):
acc = get_account(state, addr)
if acc.code == b"" or is_valid_delegation(acc.code):
# For creation txs, code hasn't been set yet on the new
# contract (set_code runs after process_message returns). Use
# evm.output which holds the code to be deployed.
if (
isinstance(message.target, Bytes0)
and addr == message.current_target
):
code = evm.output
else:
code = acc.code

# NOTE: this also matches initcode ending with empty code
# deployments via `Op.STOP` or `Op.RETURN(0, 0)`, but this
# aligns with Monad EVM implementation.
if code == b"" or is_valid_delegation(code):
original_balance = get_balance_original(state, addr)
if message.tx_env.origin == addr:
# gas_fees already deducted, need to re-add if sender
Expand All @@ -339,9 +353,13 @@ def process_message(message: Message) -> Evm:
threshold = reserve - gas_fees
else:
threshold = RESERVE_BALANCE
is_exception = not is_sender_authority(
state, addr
) and not is_valid_delegation(acc.code)

is_exception = (
message.tx_env.origin == addr
and not is_sender_authority(state, addr)
and not is_valid_delegation(code)
)

if (
acc.balance < original_balance
and acc.balance < threshold
Expand Down
56 changes: 45 additions & 11 deletions src/ethereum/forks/monad_nine/vm/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
from ..blocks import Log
from ..fork_types import Address
from ..state import (
State,
account_has_code_or_nonce,
account_has_storage,
begin_transaction,
Expand All @@ -47,7 +46,7 @@
rollback_transaction,
set_code,
)
from ..vm import Message, TransactionEnvironment
from ..vm import Message
from ..vm.eoa_delegation import (
get_delegated_code_address,
is_valid_delegation,
Expand Down Expand Up @@ -79,18 +78,49 @@
RESERVE_BALANCE = U256(10 * 10**18) # 10 MON


def is_reserve_balance_violated(
state: State,
tx_env: TransactionEnvironment,
) -> bool:
def is_reserve_balance_violated(evm: Evm) -> bool:
"""
Check if any EOA has violated the reserve balance constraint.

Returns True if a violation is detected, False otherwise.
"""
message = evm.message
state = message.block_env.state
tx_env = message.tx_env

# Collect accounts_to_delete from all ancestor frames. accounts_to_delete
# only propagates upward on success (incorporate_child_on_success), so a
# child frame like a precompile call won't see deletions from its parent.
all_accounts_to_delete: Set[Address] = set()
current_evm = evm
while True:
all_accounts_to_delete.update(current_evm.accounts_to_delete)
if current_evm.message.parent_evm is not None:
current_evm = current_evm.message.parent_evm
else:
break

for addr in set(state._main_trie._data.keys()):
# Account SELFDESTRUCTed - skip explicitly.
if addr in all_accounts_to_delete:
continue

acc = get_account(state, addr)
if acc.code == b"" or is_valid_delegation(acc.code):
# For creation txs, code hasn't been set yet on the new contract
# (set_code runs after process_message returns). Use evm.output which
# holds the code to be deployed.
if (
isinstance(message.target, Bytes0)
and addr == message.current_target
):
code = evm.output
else:
code = acc.code

# NOTE: this also matches initcode ending with empty code deployments
# via `Op.STOP` or `Op.RETURN(0, 0)`, AND check made during initcode
# execution, but this aligns with Monad EVM implementation.
if code == b"" or is_valid_delegation(code):
original_balance = get_balance_original(state, addr)
if tx_env.origin == addr:
# gas_fees already deducted, need to re-add if sender
Expand All @@ -101,9 +131,13 @@ def is_reserve_balance_violated(
threshold = reserve - gas_fees
else:
threshold = RESERVE_BALANCE
is_exception = not is_sender_authority(
state, addr
) and not is_valid_delegation(acc.code)

is_exception = (
message.tx_env.origin == addr
and not is_sender_authority(state, addr)
and not is_valid_delegation(code)
)

if (
acc.balance < original_balance
and acc.balance < threshold
Expand Down Expand Up @@ -368,7 +402,7 @@ def process_message(message: Message) -> Evm:
else:
# FIXME: index_in_block is a proxy for not being a system tx
if message.depth == 0 and message.tx_env.index_in_block is not None:
if is_reserve_balance_violated(state, message.tx_env):
if is_reserve_balance_violated(evm):
rollback_transaction(state, transient_storage)
evm.error = RevertOnReserveBalance()
return evm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ def reserve_balance(evm: Evm) -> None:
raise RevertInMonadPrecompile

# OPERATION
violation = is_reserve_balance_violated(
evm.message.block_env.state,
evm.message.tx_env,
)
violation = is_reserve_balance_violated(evm)
# Return bool encoded as uint256 (32 bytes)
evm.output = U256(1 if violation else 0).to_be_bytes32()
56 changes: 40 additions & 16 deletions tests/cancun/eip6780_selfdestruct/test_selfdestruct.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
Transaction,
compute_create_address,
)
from execution_testing.forks import Cancun
from execution_testing.forks import MONAD_EIGHT, Cancun

REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md"
REFERENCE_SPEC_VERSION = "1b6a0e94cc47e859b9866e570391cf37dc55059a"
Expand Down Expand Up @@ -378,6 +378,7 @@ def test_self_destructing_initcode(
# in the same tx
selfdestruct_contract_initial_balance: int,
fork_extra_gas: int,
fork: Fork,
) -> None:
"""
Test that a contract can self-destruct in its initcode.
Expand Down Expand Up @@ -481,15 +482,26 @@ def test_self_destructing_initcode(

entry_code_address = tx.created_contract

post: Dict[Address, Account] = {
entry_code_address: Account(
storage=entry_code_storage,
),
selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore
sendall_recipient_addresses[0]: Account(
balance=sendall_amount, storage={0: 1}
),
}
reverted = (
fork == MONAD_EIGHT and selfdestruct_contract_initial_balance > 0
)

if reverted:
post: Dict[Address, Account] = {
selfdestruct_contract_address: Account(
balance=selfdestruct_contract_initial_balance,
),
}
else:
post = {
entry_code_address: Account(
storage=entry_code_storage,
),
selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore
sendall_recipient_addresses[0]: Account(
balance=sendall_amount, storage={0: 1}
),
}

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

Expand All @@ -509,6 +521,7 @@ def test_self_destructing_initcode_create_tx(
sendall_recipient_addresses: List[Address],
selfdestruct_contract_initial_balance: int,
fork_extra_gas: int,
fork: Fork,
) -> None:
"""
Use a Create Transaction to execute a self-destructing initcode.
Expand Down Expand Up @@ -537,12 +550,23 @@ def test_self_destructing_initcode_create_tx(
# contract
sendall_amount = selfdestruct_contract_initial_balance + tx_value

post: Dict[Address, Account] = {
selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore
sendall_recipient_addresses[0]: Account(
balance=sendall_amount, storage={0: 1}
),
}
reverted = (
fork == MONAD_EIGHT and selfdestruct_contract_initial_balance > 0
)

if reverted:
post: Dict[Address, Account] = {
selfdestruct_contract_address: Account(
balance=selfdestruct_contract_initial_balance,
),
}
else:
post = {
selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore
sendall_recipient_addresses[0]: Account(
balance=sendall_amount, storage={0: 1}
),
}

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

Expand Down
Loading
Loading