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
10 changes: 10 additions & 0 deletions packages/testing/src/execution_testing/specs/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,16 @@ def verify_transaction_receipt(
expected_value=expected_receipt.cumulative_gas_used,
actual_value=actual_receipt.cumulative_gas_used,
)
if (
expected_receipt.status is not None
and actual_receipt.status != expected_receipt.status
):
raise TransactionReceiptMismatchError(
index=transaction_index,
field_name="status",
expected_value=expected_receipt.status,
actual_value=actual_receipt.status,
)
if expected_receipt.logs is not None and actual_receipt.logs is not None:
actual_logs = actual_receipt.logs
expected_logs = expected_receipt.logs
Expand Down
135 changes: 134 additions & 1 deletion tests/monad_eight/reserve_balance/test_multi_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def test_credit(
) -> None:
"""
Test reserve balance violations for an EOA sending txs with various values,
where the exception rules are not enforced based on txs in invalid block.
but also receiving a refill of entire reserve balance in the meantime.
"""
# gas spend by transactions send in setup blocks
prepare_tx_gas = (
Expand Down Expand Up @@ -461,6 +461,139 @@ def test_credit(
)


@pytest.mark.parametrize("pre_delegated", [True, False])
@pytest.mark.parametrize("send_pos", [(0, 0), (2, 0)])
@pytest.mark.parametrize("credit_pos", [(0, 0), (0, 1), (1, 0), (2, 1)])
@pytest.mark.parametrize(
"send_value",
[
pytest.param(0, id="send_zero"),
pytest.param(1, id="send_one"),
pytest.param(Spec.RESERVE_BALANCE, id="send_reserve"),
],
)
@pytest.mark.parametrize(
"credit_value",
[
pytest.param(0, id="credit_zero"),
pytest.param(1, id="credit_one"),
pytest.param(Spec.RESERVE_BALANCE, id="credit_reserve"),
],
)
@pytest.mark.parametrize("credit_statically_visible", [True, False])
def test_credit_with_value(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
pre_delegated: bool,
send_pos: Tuple[int, int],
credit_pos: Tuple[int, int],
send_value: int,
credit_value: int,
credit_statically_visible: bool,
fork: Fork,
) -> None:
"""
Test reserve balance where sender transfers value in a setup tx
and receives credit of varying amounts via direct transfer or
SELFDESTRUCT contract.

Uses 4 blocks so send in block 0 falls outside the k=3 window,
making the emptying exception reachable for undelegated senders.
"""
prepare_tx_gas = fork.gas_costs().G_TRANSACTION
prepare_tx_fee = GAS_PRICE * prepare_tx_gas
initial_balance = Spec.RESERVE_BALANCE + send_value + prepare_tx_fee

target_address = Address(0x1111)
if pre_delegated:
test_sender = pre.fund_eoa(initial_balance, delegation=target_address)
else:
test_sender = pre.fund_eoa(initial_balance)

contract = Op.SSTORE(slot_code_worked, value_code_worked) + Op.STOP
contract_address = pre.deploy_contract(contract)

nblocks = 4
blocks = []
test_sender_nonce = int(test_sender.nonce)
for nblock in range(nblocks):
txs = []
for ntx in range(2):
pos = (nblock, ntx)

if send_pos == pos:
sender = test_sender
nonce = test_sender_nonce
test_sender_nonce += 1
else:
sender = pre.fund_eoa()
nonce = 0

prepare_tx = Transaction(
gas_limit=prepare_tx_gas,
max_fee_per_gas=GAS_PRICE,
max_priority_fee_per_gas=GAS_PRICE,
to=Address(0x7873),
nonce=nonce,
sender=sender,
value=send_value,
)
txs.append(prepare_tx)

if credit_pos == pos:
if credit_statically_visible:
credit_tx = Transaction(
gas_limit=prepare_tx_gas,
max_fee_per_gas=GAS_PRICE,
max_priority_fee_per_gas=GAS_PRICE,
to=test_sender,
value=credit_value,
sender=pre.fund_eoa(),
)
else:
credit_contract = pre.deploy_contract(
Op.SELFDESTRUCT(address=test_sender),
balance=credit_value,
)
credit_tx = Transaction(
gas_limit=generous_gas(fork),
max_fee_per_gas=GAS_PRICE,
max_priority_fee_per_gas=GAS_PRICE,
to=credit_contract,
sender=pre.fund_eoa(),
)
txs.append(credit_tx)
if nblock < nblocks - 1:
blocks.append(Block(txs=txs))
del txs

value = 1
test_tx = Transaction(
gas_limit=generous_gas(fork),
max_fee_per_gas=GAS_PRICE,
max_priority_fee_per_gas=GAS_PRICE,
to=contract_address,
nonce=test_sender_nonce,
value=value,
sender=test_sender,
)
txs.append(test_tx)
blocks.append(Block(txs=txs))

# Sender balance at test time: RESERVE_BALANCE + credit_value.
violation = credit_value < value
recent_send = send_pos[0] > 0
is_exception = not pre_delegated and not recent_send
reverted = violation and not is_exception
storage = {} if reverted else {slot_code_worked: value_code_worked}

blockchain_test(
pre=pre,
post={contract_address: Account(storage=storage)},
blocks=blocks,
)


@pytest.mark.parametrize(
["value", "balance", "violation"],
[
Expand Down
169 changes: 162 additions & 7 deletions tests/monad_nine/mip4_checkreservebalance/test_precompile_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
Transaction,
)
from execution_testing.forks.helpers import Fork
from execution_testing.test_types.receipt_types import TransactionReceipt

from ..mip3_linear_memory.spec import Spec as SpecMIP3
from .helpers import (
Expand Down Expand Up @@ -655,15 +656,10 @@ def test_call_with_value(
}

_CHECK_ORDER_PAIRS = [
pytest.param(
s1,
s2,
id=f"{s1.name.lower()}__{s2.name.lower()}",
)
pytest.param(s1, s2, id=f"{s1.name.lower()}__{s2.name.lower()}")
for s1 in CallScenario
for s2 in CallScenario
if s1 != CallScenario.SUCCESS
and s2 != CallScenario.SUCCESS
if CallScenario.SUCCESS not in {s1, s2}
and s1.check_priority < s2.check_priority
and frozenset({s1, s2}) not in _INCOMPATIBLE_SCENARIOS
]
Expand Down Expand Up @@ -750,3 +746,162 @@ def test_check_order(
},
blocks=[Block(txs=[tx])],
)


# --- Direct-transaction tests ---


def _tx_params(
*scenarios: CallScenario,
pre: Alloc,
fork: Fork,
) -> tuple[bytes, int, Address, int]:
"""
Return (calldata, value, to, gas_limit) for a set of
tx-level CallScenarios.
"""
scenario_set = set(scenarios)
if CallScenario.WRONG_SELECTOR in scenario_set:
calldata = bytes.fromhex("DEADBEEF")
else:
calldata = Spec.DIPPED_INTO_RESERVE_SELECTOR

if CallScenario.SHORT_CALLDATA in scenario_set:
calldata = calldata[:3]
elif CallScenario.EXTRA_CALLDATA in scenario_set:
calldata = calldata + b"\xff"

if CallScenario.NONZERO_VALUE in scenario_set:
value = 1
else:
value = 0

if CallScenario.DELEGATE_TO_PRECOMPILE in scenario_set:
to: Address = pre.fund_eoa(
0, delegation=Spec.RESERVE_BALANCE_PRECOMPILE
)
else:
to = Spec.RESERVE_BALANCE_PRECOMPILE

if CallScenario.LOW_GAS in scenario_set:
intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(
calldata=calldata,
return_cost_deducted_prior_execution=True,
)
gas_limit = intrinsic_gas + Spec.GAS_COST - 1
else:
gas_limit = generous_gas(fork)

return calldata, value, to, gas_limit


@pytest.mark.parametrize(
"scenario",
[s for s in CallScenario if s is not CallScenario.NOT_CALL],
)
def test_tx_revert_scenarios(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
scenario: CallScenario,
) -> None:
"""
Test precompile behavior when called directly as the transaction
`to`.
"""
gas_price = 10

calldata, value, to, gas_limit = _tx_params(scenario, pre=pre, fork=fork)
gas_cost = gas_limit * gas_price
sender = pre.fund_eoa(gas_cost + value)

tx = Transaction(
gas_limit=gas_limit,
max_fee_per_gas=gas_price,
max_priority_fee_per_gas=gas_price,
to=to,
sender=sender,
data=calldata,
value=value,
expected_receipt=TransactionReceipt(
status=1 if scenario.should_succeed else 0,
),
)

post: dict = {
sender: Account(balance=value),
}

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


_TX_INCOMPATIBLE_SCENARIOS = _INCOMPATIBLE_SCENARIOS | {
# EIP-7623 floor makes it impossible to create a valid tx with
# insufficient execution gas when extra calldata is appended.
# For extra calldata (5 bytes), the floor is high enough that
# we can't create a valid tx with less than 100 execution gas
# EIP-7623 floor (21200) vs (21179) - impossible
# For correct calldata (4 bytes), in the test just above it's
# EIP-7623 floor (21160) vs (21163) - possible
frozenset({CallScenario.LOW_GAS, CallScenario.EXTRA_CALLDATA}),
}

_TX_SCENARIO_PAIRS = [
pytest.param(s1, s2, id=f"{s1.name.lower()}__{s2.name.lower()}")
for s1 in CallScenario
for s2 in CallScenario
if CallScenario.SUCCESS not in {s1, s2}
and CallScenario.NOT_CALL not in {s1, s2}
and s1.check_priority < s2.check_priority
and frozenset({s1, s2}) not in _TX_INCOMPATIBLE_SCENARIOS
]


@pytest.mark.parametrize("scenario1,scenario2", _TX_SCENARIO_PAIRS)
def test_tx_revert_scenario_pairs(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
scenario1: CallScenario,
scenario2: CallScenario,
) -> None:
"""
Test when the precompile is called directly as transaction
`to` with 2 reasons to revert.
"""
gas_price = 10

calldata, value, to, gas_limit = _tx_params(
scenario1, scenario2, pre=pre, fork=fork
)
gas_cost = gas_limit * gas_price
sender = pre.fund_eoa(gas_cost + value)

tx = Transaction(
gas_limit=gas_limit,
max_fee_per_gas=gas_price,
max_priority_fee_per_gas=gas_price,
to=to,
sender=sender,
data=calldata,
value=value,
expected_receipt=TransactionReceipt(
status=0x1
if scenario1.should_succeed and scenario2.should_succeed
else 0x0
),
)

post: dict = {
sender: Account(balance=value),
}

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