Skip to content

Commit 4f5e816

Browse files
authored
MIP-8 tests
Co-Authored-By: Claude <claude-opus-4-7>
1 parent e71afc4 commit 4f5e816

5 files changed

Lines changed: 532 additions & 8 deletions

File tree

tests/monad_ten/mip8_pageified_storage/spec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class ReferenceSpec:
1212

1313

1414
ref_spec_8 = ReferenceSpec(
15-
"MIPS/MIP-8.md", "1d3d530310a957528bd7a5c52cec853e56550b1e"
15+
"MIPS/MIP-8.md", "90af59b5e09538c5fb55b48a656a20e73fdb9373"
1616
)
1717

1818

tests/monad_ten/mip8_pageified_storage/test_cross_call.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,126 @@ def test_initcode_state_growth_persists_to_post_deploy(
907907
)
908908

909909

910+
def test_creation_tx_initcode_sload_warming(
911+
state_test: StateTestFiller,
912+
pre: Alloc,
913+
fork: Fork,
914+
) -> None:
915+
"""
916+
SLOAD inside a top-level creation-tx initcode follows
917+
cold/warm rules on the new contract's pages.
918+
"""
919+
overhead = Op.PUSH1(0).gas_cost(fork)
920+
initcode = Initcode(
921+
deploy_code=Op.STOP,
922+
initcode_prefix=(
923+
CodeGasMeasure(
924+
code=Op.SLOAD(0),
925+
overhead_cost=overhead,
926+
extra_stack_items=1,
927+
sstore_key=slot_gas_measured,
928+
stop=False,
929+
)
930+
+ CodeGasMeasure(
931+
code=Op.SLOAD(1),
932+
overhead_cost=overhead,
933+
extra_stack_items=1,
934+
sstore_key=slot_gas_measured_2,
935+
stop=False,
936+
)
937+
),
938+
)
939+
sender = pre.fund_eoa()
940+
new_contract_address = compute_create_address(
941+
address=sender, nonce=sender.nonce, opcode=Op.CREATE
942+
)
943+
944+
tx = Transaction(
945+
gas_limit=generous_gas_with_create(fork),
946+
to=None,
947+
data=bytes(initcode),
948+
sender=sender,
949+
)
950+
951+
state_test(
952+
pre=pre,
953+
post={
954+
new_contract_address: Account(
955+
storage={
956+
slot_gas_measured: Op.SLOAD(page_load_warm=False).gas_cost(
957+
fork
958+
),
959+
slot_gas_measured_2: Op.SLOAD(
960+
page_load_warm=True
961+
).gas_cost(fork),
962+
},
963+
),
964+
},
965+
tx=tx,
966+
)
967+
968+
969+
def test_creation_tx_initcode_sstore_warming(
970+
state_test: StateTestFiller,
971+
pre: Alloc,
972+
fork: Fork,
973+
) -> None:
974+
"""
975+
SSTORE inside a top-level creation-tx initcode follows
976+
cold/warm + state-growth rules on the new contract's pages.
977+
"""
978+
overhead = (Op.PUSH1(0) + Op.PUSH1(0)).gas_cost(fork)
979+
initcode = Initcode(
980+
deploy_code=Op.STOP,
981+
initcode_prefix=(
982+
CodeGasMeasure(
983+
code=Op.SSTORE(0, 1),
984+
overhead_cost=overhead,
985+
extra_stack_items=0,
986+
sstore_key=slot_gas_measured,
987+
stop=False,
988+
)
989+
+ CodeGasMeasure(
990+
code=Op.SSTORE(1, 1),
991+
overhead_cost=overhead,
992+
extra_stack_items=0,
993+
sstore_key=slot_gas_measured_2,
994+
stop=False,
995+
)
996+
),
997+
)
998+
sender = pre.fund_eoa()
999+
new_contract_address = compute_create_address(
1000+
address=sender, nonce=sender.nonce, opcode=Op.CREATE
1001+
)
1002+
1003+
page = TxPageState()
1004+
expected_first_gas = simulate_sstore(page, 0, 1, fork)
1005+
expected_second_gas = simulate_sstore(page, 1, 1, fork)
1006+
1007+
tx = Transaction(
1008+
gas_limit=generous_gas_with_create(fork),
1009+
to=None,
1010+
data=bytes(initcode),
1011+
sender=sender,
1012+
)
1013+
1014+
state_test(
1015+
pre=pre,
1016+
post={
1017+
new_contract_address: Account(
1018+
storage={
1019+
0: 1,
1020+
1: 1,
1021+
slot_gas_measured: expected_first_gas,
1022+
slot_gas_measured_2: expected_second_gas,
1023+
},
1024+
),
1025+
},
1026+
tx=tx,
1027+
)
1028+
1029+
9101030
@pytest.mark.parametrize(
9111031
"call_kind",
9121032
["call", "callcode", "delegatecall_chain", "call_chain"],
@@ -972,6 +1092,117 @@ def test_cross_account_page_propagation(
9721092
)
9731093

9741094

1095+
@pytest.mark.parametrize(
1096+
"call_kind",
1097+
["callcode", "delegatecall"],
1098+
)
1099+
def test_cross_account_caller_warm_propagates(
1100+
state_test: StateTestFiller,
1101+
pre: Alloc,
1102+
fork: Fork,
1103+
call_kind: str,
1104+
) -> None:
1105+
"""CALLCODE/DELEGATECALL preserve caller's storage-context pages."""
1106+
overhead = Op.PUSH1(0).gas_cost(fork)
1107+
inner = pre.deploy_contract(
1108+
CodeGasMeasure(
1109+
code=Op.SLOAD(0),
1110+
overhead_cost=overhead,
1111+
extra_stack_items=1,
1112+
sstore_key=slot_gas_measured,
1113+
)
1114+
)
1115+
1116+
sub_call = (
1117+
Op.CALLCODE(address=inner)
1118+
if call_kind == "callcode"
1119+
else Op.DELEGATECALL(address=inner)
1120+
)
1121+
outer = pre.deploy_contract(Op.SLOAD(0) + sub_call)
1122+
1123+
tx = Transaction(
1124+
gas_limit=generous_gas(fork),
1125+
to=outer,
1126+
sender=pre.fund_eoa(),
1127+
)
1128+
1129+
state_test(
1130+
pre=pre,
1131+
post={
1132+
outer: Account(
1133+
storage={
1134+
slot_gas_measured: Op.SLOAD(page_load_warm=True).gas_cost(
1135+
fork
1136+
),
1137+
},
1138+
),
1139+
},
1140+
tx=tx,
1141+
)
1142+
1143+
1144+
def test_delegated_eoa_owns_pages(
1145+
state_test: StateTestFiller,
1146+
pre: Alloc,
1147+
fork: Fork,
1148+
) -> None:
1149+
"""
1150+
Pages warmed inside delegated-EOA code are keyed by the EOA
1151+
address.
1152+
"""
1153+
overhead = Op.PUSH1(0).gas_cost(fork)
1154+
delegate_code = Conditional(
1155+
condition=Op.CALLDATASIZE,
1156+
if_true=CodeGasMeasure(
1157+
code=Op.SLOAD(0),
1158+
overhead_cost=overhead,
1159+
extra_stack_items=1,
1160+
sstore_key=slot_gas_measured_2,
1161+
),
1162+
if_false=CodeGasMeasure(
1163+
code=Op.SLOAD(0),
1164+
overhead_cost=overhead,
1165+
extra_stack_items=1,
1166+
sstore_key=slot_gas_measured,
1167+
),
1168+
)
1169+
delegate_target = pre.deploy_contract(delegate_code)
1170+
eoa1 = pre.fund_eoa(delegation=delegate_target)
1171+
eoa2 = pre.fund_eoa(delegation=delegate_target)
1172+
1173+
runner_code = (
1174+
Op.CALL(address=delegate_target)
1175+
+ Op.CALL(address=eoa1)
1176+
+ Op.CALL(address=eoa2)
1177+
+ Op.CALL(address=eoa2, args_size=1)
1178+
)
1179+
runner_address = pre.deploy_contract(runner_code)
1180+
1181+
tx = Transaction(
1182+
gas_limit=generous_gas(fork),
1183+
to=runner_address,
1184+
sender=pre.fund_eoa(),
1185+
)
1186+
1187+
cold = Op.SLOAD(page_load_warm=False).gas_cost(fork)
1188+
warm = Op.SLOAD(page_load_warm=True).gas_cost(fork)
1189+
1190+
state_test(
1191+
pre=pre,
1192+
post={
1193+
delegate_target: Account(storage={slot_gas_measured: cold}),
1194+
eoa1: Account(storage={slot_gas_measured: cold}),
1195+
eoa2: Account(
1196+
storage={
1197+
slot_gas_measured: cold,
1198+
slot_gas_measured_2: warm,
1199+
},
1200+
),
1201+
},
1202+
tx=tx,
1203+
)
1204+
1205+
9751206
@pytest.mark.parametrize(
9761207
"revert_cause",
9771208
[

tests/monad_ten/mip8_pageified_storage/test_fork_transition.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
Alloc,
1010
Block,
1111
BlockchainTestFiller,
12+
Bytecode,
1213
CodeGasMeasure,
1314
Conditional,
1415
Hash,
1516
Op,
17+
Storage,
1618
Transaction,
1719
)
1820
from execution_testing.forks import MONAD_NEXT, MONAD_NINE
@@ -398,3 +400,98 @@ def test_access_list_warming_at_fork(
398400
),
399401
},
400402
)
403+
404+
405+
@pytest.mark.valid_at_transition_to("MONAD_NEXT")
406+
def test_blockhash_stable_across_fork(
407+
blockchain_test: BlockchainTestFiller,
408+
pre: Alloc,
409+
fork: Fork,
410+
) -> None:
411+
"""
412+
BLOCKHASH of pre-fork blocks stays the same when queried post-fork.
413+
414+
MIP-8 changes the state-root commitment; pre-fork block hashes must
415+
not change when read post-fork.
416+
"""
417+
sender = pre.fund_eoa()
418+
419+
def slot_blockhash(i: int) -> Bytecode:
420+
return Op.ADD(Op.MUL(Op.TIMESTAMP, 16), i)
421+
422+
def slot_nonzero(i: int) -> Bytecode:
423+
return Op.ADD(Op.MUL(Op.TIMESTAMP, 16), 4 + i)
424+
425+
def prev_slot(i: int) -> Bytecode:
426+
return Op.ADD(Op.MUL(Op.SUB(Op.TIMESTAMP, 1), 16), i)
427+
428+
slot_stable = Op.MUL(Op.TIMESTAMP, 16)
429+
430+
def stable_i(i: int) -> Bytecode:
431+
return Op.OR(
432+
Op.ISZERO(Op.SLOAD(prev_slot(i))),
433+
Op.EQ(Op.SLOAD(prev_slot(i)), Op.BLOCKHASH(i)),
434+
)
435+
436+
contract_code = (
437+
Op.SSTORE(slot_blockhash(1), Op.BLOCKHASH(1))
438+
+ Op.SSTORE(slot_blockhash(2), Op.BLOCKHASH(2))
439+
+ Op.SSTORE(slot_blockhash(3), Op.BLOCKHASH(3))
440+
+ Op.SSTORE(slot_nonzero(1), Op.ISZERO(Op.ISZERO(Op.BLOCKHASH(1))))
441+
+ Op.SSTORE(slot_nonzero(2), Op.ISZERO(Op.ISZERO(Op.BLOCKHASH(2))))
442+
+ Op.SSTORE(slot_nonzero(3), Op.ISZERO(Op.ISZERO(Op.BLOCKHASH(3))))
443+
+ Op.SSTORE(
444+
slot_stable,
445+
Op.AND(Op.AND(stable_i(1), stable_i(2)), stable_i(3)),
446+
)
447+
)
448+
contract_address = pre.deploy_contract(contract_code)
449+
450+
timestamps = [14_998, 14_999, 15_000, 15_001]
451+
blocks = [
452+
Block(
453+
timestamp=ts,
454+
txs=[
455+
Transaction(
456+
to=contract_address,
457+
sender=sender,
458+
nonce=i,
459+
gas_limit=generous_gas(fork),
460+
),
461+
],
462+
)
463+
for i, ts in enumerate(timestamps)
464+
]
465+
466+
# Per-timestamp tuple is (is_nonzero(BLOCKHASH(1)),
467+
# is_nonzero(BLOCKHASH(2)), is_nonzero(BLOCKHASH(3))) computed
468+
# during that block. BLOCKHASH(n) is non-zero iff n is a past
469+
# block (1 <= n < current_block_number).
470+
# ts=14_998 -> block 1: queries blocks 1,2,3 — all current/future
471+
# ts=14_999 -> block 2: block 1 is past, 2 is current, 3 future
472+
# ts=15_000 -> block 3 (post-fork): blocks 1,2 past, 3 current
473+
# ts=15_001 -> block 4 (post-fork): blocks 1,2,3 all past
474+
nonzero_pattern = {
475+
14_998: (0, 0, 0),
476+
14_999: (1, 0, 0),
477+
15_000: (1, 1, 0),
478+
15_001: (1, 1, 1),
479+
}
480+
# Per-block slot layout (offset within ts*16 base):
481+
storage = Storage()
482+
for ts in timestamps:
483+
# ts*16: is BLOCKHASH stable
484+
storage[ts * 16] = 1
485+
for i in (1, 2, 3):
486+
flag = nonzero_pattern[ts][i - 1]
487+
if flag:
488+
# ts*16 + 1..+3 : BLOCKHASH(1..3) value
489+
storage.set_expect_any(ts * 16 + i)
490+
# ts*16 + 5..+7 : is_nonzero(BLOCKHASH(1..3))
491+
storage[ts * 16 + 4 + i] = flag
492+
493+
blockchain_test(
494+
pre=pre,
495+
blocks=blocks,
496+
post={contract_address: Account(storage=storage)},
497+
)

0 commit comments

Comments
 (0)