Skip to content

Commit e313e82

Browse files
shemnonmarioevz
andauthored
new(tests): EOF - EIP-7069, EIP-7480, EIP-7620: Add memory expansion tests to EOF opcodes (#989)
* Test memory expansion limits Test EOF instructions memory expansion along select memory limits. Signed-off-by: Danno Ferrin <danno@numisight.com> * Add EOFCREATE and Length checking Signed-off-by: Danno Ferrin <danno@numisight.com> * Add RETURNDATACOPY, DATACOPY, and RETURNCONTRACT Signed-off-by: Danno Ferrin <danno@numisight.com> * format opcodes Signed-off-by: Danno Ferrin <danno@numisight.com> * test description update Signed-off-by: Danno Ferrin <danno@numisight.com> * review changes Signed-off-by: Danno Ferrin <danno@numisight.com> * fix(tests): tox --------- Signed-off-by: Danno Ferrin <danno@numisight.com> Co-authored-by: Mario Vega <marioevz@gmail.com>
1 parent 16c45c6 commit e313e82

File tree

6 files changed

+388
-30
lines changed

6 files changed

+388
-30
lines changed

src/ethereum_test_vm/opcode.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5117,7 +5117,13 @@ class Opcodes(Opcode, Enum):
51175117
51185118
"""
51195119

5120-
RETURNCONTRACT = Opcode(0xEE, popped_stack_items=2, data_portion_length=1, terminating=True)
5120+
RETURNCONTRACT = Opcode(
5121+
0xEE,
5122+
popped_stack_items=2,
5123+
data_portion_length=1,
5124+
terminating=True,
5125+
kwargs=["auxdata_offset", "auxdata_size"],
5126+
)
51215127
"""
51225128
!!! Note: This opcode is under development
51235129

tests/osaka/eip7692_eof_v1/eip7069_extcall/test_calldata.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
slot_calldata_2,
1919
slot_code_worked,
2020
slot_delegate_code_worked,
21+
slot_eof_target_returndata,
2122
value_calldata_1,
2223
value_calldata_2,
2324
value_code_worked,
@@ -451,3 +452,111 @@ def test_calldata_remains_after_subcall(
451452
tx=tx,
452453
post=post,
453454
)
455+
456+
457+
@pytest.mark.parametrize("operation", [Op.EXTCALL, Op.EXTSTATICCALL, Op.EXTDELEGATECALL])
458+
@pytest.mark.parametrize(
459+
"offset_field",
460+
[
461+
pytest.param(True, id="offset"),
462+
pytest.param(False, id="size"),
463+
],
464+
)
465+
@pytest.mark.parametrize(
466+
("test_arg", "success"),
467+
[
468+
pytest.param(0, True, id="zero"),
469+
pytest.param(0xFF, True, id="8-bit"),
470+
pytest.param(0x100, True, id="9-bit"),
471+
pytest.param(0xFFFF, True, id="16-bit"),
472+
pytest.param(0x10000, True, id="17-bit"),
473+
pytest.param(0x1FFFF20, False, id="32-bit-mem-cost"),
474+
pytest.param(0x2D412E0, False, id="33-bit-mem-cost"),
475+
pytest.param(0xFFFFFFFF, False, id="32-bit"),
476+
pytest.param(0x100000000, False, id="33-bit"),
477+
pytest.param(0x1FFFFFFFF20, False, id="64-bit-mem-cost"),
478+
pytest.param(0x2D413CCCF00, False, id="65-bit-mem-cost"),
479+
pytest.param(0xFFFFFFFFFFFFFFFF, False, id="64-bit"),
480+
pytest.param(0x10000000000000000, False, id="65-bit"),
481+
pytest.param(0xFFFFFFFFFFFFFFFF, False, id="128-bit"),
482+
pytest.param(0x10000000000000000, False, id="129-bit"),
483+
pytest.param(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, False, id="256-bit"),
484+
],
485+
)
486+
def test_extcalls_input_offset(
487+
state_test: StateTestFiller,
488+
pre: Alloc,
489+
operation: Op,
490+
offset_field: str,
491+
test_arg: int,
492+
success: bool,
493+
):
494+
"""
495+
Tests call data into EXT*CALL including multiple offset conditions.
496+
497+
Returner returns a success value, which caller stores. If memory expansion cost is less than
498+
2 billion gas call succeeds. Else whole transaction aborts, leaving canaries in memory.
499+
500+
The name id of `*-mem-cost` refers to the bit-length of the result of the calculated memory
501+
expansion cost. Their length choice is designed to cause problems on shorter bit-length
502+
representations with native integers.
503+
504+
The `offset_field` param indicates what part of the input data arguments are being tested,
505+
either the offset of the data in memory or the size of the data in memory.
506+
507+
The `test_arg` param is the value passed into the field being tested (offset or size),
508+
intending to trigger integer size bugs for that particular field.
509+
"""
510+
env = Environment()
511+
512+
sender = pre.fund_eoa()
513+
514+
address_returner = pre.deploy_contract(
515+
Container(
516+
sections=[
517+
Section.Code(code=Op.MSTORE(0, value_code_worked) + Op.RETURN(0, 32)),
518+
]
519+
),
520+
)
521+
address_caller = pre.deploy_contract(
522+
Container(
523+
sections=[
524+
Section.Code(
525+
code=(
526+
operation(address=address_returner, args_offset=test_arg, args_size=32)
527+
if offset_field
528+
else operation(
529+
address=address_returner, args_offset=32, args_size=test_arg
530+
)
531+
)
532+
+ Op.SSTORE(slot_eof_target_returndata, Op.RETURNDATALOAD(0))
533+
+ Op.SSTORE(slot_code_worked, value_code_worked)
534+
+ Op.STOP
535+
)
536+
]
537+
),
538+
storage={
539+
slot_code_worked: value_exceptional_abort_canary,
540+
slot_eof_target_returndata: value_exceptional_abort_canary,
541+
},
542+
)
543+
544+
post = {
545+
address_caller: Account(
546+
storage={
547+
slot_eof_target_returndata: value_code_worked
548+
if success
549+
else value_exceptional_abort_canary,
550+
slot_code_worked: value_code_worked if success else value_exceptional_abort_canary,
551+
}
552+
),
553+
}
554+
555+
tx = Transaction(to=address_caller, gas_limit=1_000_000_000, sender=sender)
556+
557+
state_test(
558+
env=env,
559+
pre=pre,
560+
tx=tx,
561+
post=post,
562+
)

tests/osaka/eip7692_eof_v1/eip7069_extcall/test_returndatacopy_memory_expansion.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -225,20 +225,20 @@ def test_returndatacopy_memory_expansion(
225225
@pytest.mark.parametrize(
226226
"dest,src,length",
227227
[
228-
(2**256 - 1, 0x00, 0x01),
229-
(2**256 - 2, 0x00, 0x01),
230-
(2**255 - 1, 0x00, 0x01),
231-
(0x00, 0x00, 2**256 - 1),
232-
(0x00, 0x00, 2**256 - 2),
233-
(0x00, 0x00, 2**255 - 1),
234-
],
235-
ids=[
236-
"max_dest_single_byte_expansion",
237-
"max_dest_minus_one_single_byte_expansion",
238-
"half_max_dest_single_byte_expansion",
239-
"max_length_expansion",
240-
"max_length_minus_one_expansion",
241-
"half_max_length_expansion",
228+
pytest.param(2**256 - 1, 0x00, 0x01, id="max_dest_single_byte_expansion"),
229+
pytest.param(2**256 - 2, 0x00, 0x01, id="max_dest_minus_one_single_byte_expansion"),
230+
pytest.param(2**255 - 1, 0x00, 0x01, id="half_max_dest_single_byte_expansion"),
231+
pytest.param(0x00, 0x00, 2**256 - 1, id="max_length_expansion"),
232+
pytest.param(0x00, 0x00, 2**256 - 2, id="max_length_minus_one_expansion"),
233+
pytest.param(0x00, 0x00, 2**255 - 1, id="half_max_length_expansion"),
234+
pytest.param(0x1FFFF20, 0x00, 0x01, id="32-bit-mem-cost_offset"),
235+
pytest.param(0x2D412E0, 0x00, 0x01, id="33-bit-mem-cost_offset"),
236+
pytest.param(0x00, 0x00, 0x1FFFF20, id="32-bit-mem-cost_size"),
237+
pytest.param(0x00, 0x00, 0x2D412E0, id="33-bit-mem-cost_size"),
238+
pytest.param(0x1FFFFFFFF20, 0x00, 0x01, id="64-bit-mem-cost_offset"),
239+
pytest.param(0x2D413CCCF00, 0x00, 0x01, id="65-bit-mem-cost_offset"),
240+
pytest.param(0x00, 0x00, 0x1FFFFFFFF20, id="64-bit-mem-cost_size"),
241+
pytest.param(0x00, 0x00, 0x2D413CCCF00, id="65-bit-mem-cost_size"),
242242
],
243243
)
244244
@pytest.mark.parametrize(

tests/osaka/eip7692_eof_v1/eip7480_data_section/test_datacopy_memory_expansion.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -235,20 +235,20 @@ def test_datacopy_memory_expansion(
235235
@pytest.mark.parametrize(
236236
"dest,src,length",
237237
[
238-
(2**256 - 1, 0x00, 0x01),
239-
(2**256 - 2, 0x00, 0x01),
240-
(2**255 - 1, 0x00, 0x01),
241-
(0x00, 0x00, 2**256 - 1),
242-
(0x00, 0x00, 2**256 - 2),
243-
(0x00, 0x00, 2**255 - 1),
244-
],
245-
ids=[
246-
"max_dest_single_byte_expansion",
247-
"max_dest_minus_one_single_byte_expansion",
248-
"half_max_dest_single_byte_expansion",
249-
"max_length_expansion",
250-
"max_length_minus_one_expansion",
251-
"half_max_length_expansion",
238+
pytest.param(2**256 - 1, 0x00, 0x01, id="max_dest_single_byte_expansion"),
239+
pytest.param(2**256 - 2, 0x00, 0x01, id="max_dest_minus_one_single_byte_expansion"),
240+
pytest.param(2**255 - 1, 0x00, 0x01, id="half_max_dest_single_byte_expansion"),
241+
pytest.param(0x00, 0x00, 2**256 - 1, id="max_length_expansion"),
242+
pytest.param(0x00, 0x00, 2**256 - 2, id="max_length_minus_one_expansion"),
243+
pytest.param(0x00, 0x00, 2**255 - 1, id="half_max_length_expansion"),
244+
pytest.param(0x1FFFF20, 0x00, 0x01, id="32-bit-mem-cost_offset"),
245+
pytest.param(0x2D412E0, 0x00, 0x01, id="33-bit-mem-cost_offset"),
246+
pytest.param(0x00, 0x00, 0x1FFFF20, id="32-bit-mem-cost_size"),
247+
pytest.param(0x00, 0x00, 0x2D412E0, id="33-bit-mem-cost_size"),
248+
pytest.param(0x1FFFFFFFF20, 0x00, 0x01, id="64-bit-mem-cost_offset"),
249+
pytest.param(0x2D413CCCF00, 0x00, 0x01, id="65-bit-mem-cost_offset"),
250+
pytest.param(0x00, 0x00, 0x1FFFFFFFF20, id="64-bit-mem-cost_size"),
251+
pytest.param(0x00, 0x00, 0x2D413CCCF00, id="65-bit-mem-cost_size"),
252252
],
253253
)
254254
@pytest.mark.parametrize(
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""Test good and bad EOFCREATE cases."""
2+
3+
import pytest
4+
5+
from ethereum_test_base_types import Account, Storage
6+
from ethereum_test_tools import Alloc, Environment, StateTestFiller, compute_eofcreate_address
7+
from ethereum_test_tools.eof.v1 import Container, Section
8+
from ethereum_test_tools.vm.opcode import Opcodes as Op
9+
from ethereum_test_types import Transaction
10+
11+
from .. import EOF_FORK_NAME
12+
from .helpers import (
13+
slot_code_worked,
14+
slot_create_address,
15+
smallest_initcode_subcontainer,
16+
smallest_runtime_subcontainer,
17+
value_canary_to_be_overwritten,
18+
value_code_worked,
19+
)
20+
21+
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7620.md"
22+
REFERENCE_SPEC_VERSION = "52ddbcdddcf72dd72427c319f2beddeb468e1737"
23+
24+
pytestmark = pytest.mark.valid_from(EOF_FORK_NAME)
25+
26+
27+
@pytest.mark.parametrize(
28+
"offset_field",
29+
[
30+
pytest.param(True, id="offset"),
31+
pytest.param(False, id="size"),
32+
],
33+
)
34+
@pytest.mark.parametrize(
35+
("test_arg", "success"),
36+
[
37+
pytest.param(0, True, id="zero"),
38+
pytest.param(0xFF, True, id="8-bit"),
39+
pytest.param(0x100, True, id="9-bit"),
40+
pytest.param(0xFFFF, True, id="16-bit"),
41+
pytest.param(0x10000, True, id="17-bit"),
42+
pytest.param(0x1FFFF20, False, id="32-bit-mem-cost"),
43+
pytest.param(0x2D412E0, False, id="33-bit-mem-cost"),
44+
pytest.param(0xFFFFFFFF, False, id="32-bit"),
45+
pytest.param(0x100000000, False, id="33-bit"),
46+
pytest.param(0x1FFFFFFFF20, False, id="64-bit-mem-cost"),
47+
pytest.param(0x2D413CCCF00, False, id="65-bit-mem-cost"),
48+
pytest.param(0xFFFFFFFFFFFFFFFF, False, id="64-bit"),
49+
pytest.param(0x10000000000000000, False, id="65-bit"),
50+
pytest.param(0xFFFFFFFFFFFFFFFF, False, id="128-bit"),
51+
pytest.param(0x10000000000000000, False, id="129-bit"),
52+
pytest.param(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, False, id="256-bit"),
53+
],
54+
)
55+
def test_eofcreate_memory(
56+
state_test: StateTestFiller,
57+
pre: Alloc,
58+
offset_field: str,
59+
test_arg: int,
60+
success: bool,
61+
):
62+
"""
63+
Tests auxdata sizes in EOFCREATE including multiple offset conditions.
64+
65+
EOFCREATE either succeeds or fails based on memory access cost, resulting in new address
66+
or zero in the create address slot.
67+
68+
The name id of `*-mem-cost` refers to the bit-length of the result of the calculated memory
69+
expansion cost. Their length choice is designed to cause problems on shorter bit-length
70+
representations with native integers.
71+
72+
The `offset_field` param indicates what part of the input data arguments are being tested,
73+
either the offset of the data in memory or the size of the data in memory.
74+
75+
The `test_arg` param is the value passed into the field being tested (offset or size),
76+
intending to trigger integer size bugs for that particular field.
77+
"""
78+
env = Environment()
79+
80+
sender = pre.fund_eoa(10**27)
81+
82+
initial_storage = Storage(
83+
{
84+
slot_create_address: value_canary_to_be_overwritten, # type: ignore
85+
slot_code_worked: value_canary_to_be_overwritten, # type: ignore
86+
}
87+
)
88+
calling_contract_address = pre.deploy_contract(
89+
code=Container(
90+
sections=[
91+
Section.Code(
92+
code=Op.SSTORE(
93+
slot_create_address,
94+
Op.EOFCREATE[0](
95+
value=0,
96+
salt=0,
97+
input_offset=test_arg if offset_field else 32,
98+
input_size=32 if offset_field else test_arg,
99+
),
100+
)
101+
+ Op.SSTORE(slot_code_worked, value_code_worked)
102+
+ Op.STOP,
103+
),
104+
Section.Container(container=smallest_initcode_subcontainer),
105+
]
106+
),
107+
storage=initial_storage,
108+
)
109+
destination_contract_address = compute_eofcreate_address(
110+
calling_contract_address, 0, smallest_initcode_subcontainer
111+
)
112+
113+
post = {
114+
calling_contract_address: Account(
115+
storage={
116+
slot_create_address: destination_contract_address,
117+
slot_code_worked: value_code_worked,
118+
}
119+
if success
120+
else initial_storage,
121+
),
122+
destination_contract_address: Account(code=smallest_runtime_subcontainer)
123+
if success
124+
else Account.NONEXISTENT,
125+
}
126+
127+
tx = Transaction(sender=sender, to=calling_contract_address, gas_limit=2_000_000_000)
128+
129+
state_test(
130+
env=env,
131+
pre=pre,
132+
post=post,
133+
tx=tx,
134+
)

0 commit comments

Comments
 (0)