-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
new(tests): EOF - EIP-7069 - RETURNDATALOAD and RETURNDATACOPY (#595)
* Validate EIP-7676 Prepare for Address Space Extension Validate ASE behaviors on legacy and EOF opcodes for ASE, when targeting EOAs, Contracts, and empty accounts. Opcodes are BALANCE, EXTCALL/CALL, EXTDELEGATECALL/DELEGATECALL, EXTSTATICCALL/STATICCALL, and CALLCODE. Signed-off-by: Danno Ferrin <[email protected]> * Reviewer requested changes Signed-off-by: Danno Ferrin <[email protected]> * formatting Signed-off-by: Danno Ferrin <[email protected]> * speling Signed-off-by: Danno Ferrin <[email protected]> * comment out balance from ASE checks EIP-7676 moved the critical EXTCALL sections into the main spec. EOF does not depend on a new BALANCE op, so don't test it. Signed-off-by: Danno Ferrin <[email protected]> * move to new subfolder Signed-off-by: Danno Ferrin <[email protected]> * Recast as EIP-7069 test Signed-off-by: Danno Ferrin <[email protected]> * use itertools.count instead of hard coded address Signed-off-by: Danno Ferrin <[email protected]> * Review changes * Apply specific edits * Parameterize by opcode as well Signed-off-by: Danno Ferrin <[email protected]> * tox fixes Signed-off-by: Danno Ferrin <[email protected]> * remove RETURNDATALOAD from ASE tests Remove RETURNDATALOAD from ASE tests, to be moved to a different test Signed-off-by: Danno Ferrin <[email protected]> * reviewer comments Signed-off-by: Danno Ferrin <[email protected]> * new(tests) EIP-7069 RETURNDATALOAD and RETURNDATACOPY Test new RETURNDATACOPY semantics across both EOF and Legacy. Test RETURNDATALOAD semantics. Includes boundary conditions on both. Signed-off-by: Danno Ferrin <[email protected]> * review changes Signed-off-by: Danno Ferrin <[email protected]> * unneeded changes. Signed-off-by: Danno Ferrin <[email protected]> * docs: Changelog --------- Signed-off-by: Danno Ferrin <[email protected]> Co-authored-by: Mario Vega <[email protected]>
- Loading branch information
Showing
5 changed files
with
299 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
""" | ||
Revamped Call Instructions Tests | ||
""" | ||
|
||
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7069.md" | ||
REFERENCE_SPEC_VERSION = "1795943aeacc86131d5ab6bb3d65824b3b1d4cad" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
""" | ||
EOF extcall tests helpers | ||
""" | ||
import itertools | ||
|
||
"""Storage addresses for common testing fields""" | ||
_slot = itertools.count() | ||
next(_slot) # don't use slot 0 | ||
slot_code_worked = next(_slot) | ||
slot_last_slot = next(_slot) | ||
|
||
"""Storage values for common testing fields""" | ||
value_code_worked = 0x2015 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
282 changes: 282 additions & 0 deletions
282
tests/prague/eip7692_eof_v1/eip7069_extcall/test_returndataload.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
""" | ||
abstract: Tests [EIP-7069: Revamped CALL instructions](https://eips.ethereum.org/EIPS/eip-7069) | ||
Tests for the RETURNDATALOAD instriction | ||
""" # noqa: E501 | ||
from typing import List | ||
|
||
import pytest | ||
|
||
from ethereum_test_tools import ( | ||
Account, | ||
Address, | ||
Environment, | ||
StateTestFiller, | ||
TestAddress, | ||
Transaction, | ||
) | ||
from ethereum_test_tools.eof.v1 import Container, Section | ||
from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION | ||
from ethereum_test_tools.vm.opcode import Opcodes as Op | ||
|
||
from .. import EOF_FORK_NAME | ||
from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION | ||
from .helpers import slot_code_worked, value_code_worked | ||
|
||
REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH | ||
REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION | ||
|
||
pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
["call_prefix", "opcode", "call_suffix"], | ||
[ | ||
pytest.param([500_000], Op.CALL, [0, 0, 0, 0, 0], id="CALL"), | ||
pytest.param([500_000], Op.CALLCODE, [0, 0, 0, 0, 0], id="CALLCODE"), | ||
pytest.param([500_000], Op.DELEGATECALL, [0, 0, 0, 0], id="DELEGATECALL"), | ||
pytest.param([500_000], Op.STATICCALL, [0, 0, 0, 0], id="STATICCALL"), | ||
pytest.param([], Op.EXTCALL, [0, 0, 0], id="EXTCALL"), | ||
pytest.param([], Op.EXTDELEGATECALL, [0, 0], id="EXTDELEGATECALL"), | ||
pytest.param([], Op.EXTSTATICCALL, [0, 0], id="EXTSTATICCALL"), | ||
], | ||
ids=lambda x: x, | ||
) | ||
@pytest.mark.parametrize( | ||
"return_data", | ||
[ | ||
b"", | ||
b"\x10" * 0x10, | ||
b"\x20" * 0x20, | ||
b"\x30" * 0x30, | ||
], | ||
ids=lambda x: "len_%x" % len(x), | ||
) | ||
@pytest.mark.parametrize( | ||
"offset", | ||
[ | ||
0, | ||
0x10, | ||
0x20, | ||
0x30, | ||
], | ||
ids=lambda x: "offset_%x" % x, | ||
) | ||
@pytest.mark.parametrize( | ||
"size", | ||
[ | ||
0, | ||
0x10, | ||
0x20, | ||
0x30, | ||
], | ||
ids=lambda x: "size_%x" % x, | ||
) | ||
def test_returndatacopy_handling( | ||
state_test: StateTestFiller, | ||
call_prefix: List[int], | ||
opcode: Op, | ||
call_suffix: List[int], | ||
return_data: bytes, | ||
offset: int, | ||
size: int, | ||
): | ||
""" | ||
Tests ReturnDataLoad including multiple offset conditions and differeing legacy vs. eof | ||
boundary conditions. | ||
entrypoint creates a "0xff" test area of memory, delegate calls to caller. | ||
Caller is either EOF or legacy, as per parameter. Calls returner and copies the return data | ||
based on offset and size params. Cases are expected to trigger boundary violations. | ||
Entrypoint copies the test area to storage slots, and the expected result is asserted. | ||
""" | ||
env = Environment() | ||
address_entry_point = Address(0x1000000) | ||
address_caller = Address(0x1000001) | ||
address_returner = Address(0x1000002) | ||
tx = Transaction(to=address_entry_point, gas_limit=2_000_000, nonce=1) | ||
|
||
slot_result_start = 0x1000 | ||
|
||
pre = { | ||
TestAddress: Account(balance=10**18, nonce=tx.nonce), | ||
address_entry_point: Account( | ||
nonce=1, | ||
code=Op.NOOP | ||
# First, create a "dirty" area, so we can check zero overwrite | ||
+ Op.MSTORE(0x00, -1) + Op.MSTORE(0x20, -1) | ||
# call the contract under test | ||
+ Op.DELEGATECALL(1_000_000, address_caller, 0, 0, 0, 0) | ||
+ Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) | ||
# store the return data | ||
+ Op.SSTORE(slot_result_start, Op.MLOAD(0x0)) | ||
+ Op.SSTORE(slot_result_start + 1, Op.MLOAD(0x20)) | ||
+ Op.SSTORE(slot_code_worked, value_code_worked) | ||
+ Op.STOP, | ||
), | ||
address_returner: Account( | ||
nonce=1, | ||
code=Container( | ||
sections=[ | ||
Section.Code( | ||
code=Op.DATACOPY(0, 0, Op.DATASIZE) + Op.RETURN(0, Op.DATASIZE), | ||
code_outputs=NON_RETURNING_SECTION, | ||
max_stack_height=3, | ||
), | ||
Section.Data(data=return_data), | ||
] | ||
), | ||
), | ||
} | ||
|
||
result = [0xFF] * 0x40 | ||
result[0:size] = [0] * size | ||
extent = size - max(0, size + offset - len(return_data)) | ||
if extent > 0 and len(return_data) > 0: | ||
result[0:extent] = [return_data[0]] * extent | ||
post = { | ||
address_entry_point: Account( | ||
storage={ | ||
slot_code_worked: value_code_worked, | ||
slot_result_start: bytes(result[:0x20]), | ||
(slot_result_start + 0x1): bytes(result[0x20:]), | ||
} | ||
) | ||
} | ||
|
||
code_under_test: bytes = ( | ||
opcode(*call_prefix, address_returner, *call_suffix) | ||
+ Op.RETURNDATACOPY(0, offset, size) | ||
+ Op.SSTORE(slot_code_worked, value_code_worked) | ||
+ Op.RETURN(0, size) | ||
) | ||
match opcode: | ||
case Op.EXTCALL | Op.EXTDELEGATECALL | Op.EXTSTATICCALL: | ||
pre[address_caller] = Account( | ||
code=Container( | ||
sections=[ | ||
Section.Code( | ||
code=code_under_test, | ||
max_stack_height=4, | ||
) | ||
] | ||
) | ||
) | ||
case Op.CALL | Op.CALLCODE | Op.DELEGATECALL | Op.STATICCALL: | ||
pre[address_caller] = Account( | ||
code=code_under_test, | ||
) | ||
if (offset + size) > len(return_data): | ||
post[address_entry_point] = Account( | ||
storage={ | ||
slot_code_worked: value_code_worked, | ||
slot_result_start: b"\xff" * 32, | ||
slot_result_start + 1: b"\xff" * 32, | ||
} | ||
) | ||
|
||
state_test( | ||
env=env, | ||
pre=pre, | ||
tx=tx, | ||
post=post, | ||
) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
["opcode", "call_suffix"], | ||
[ | ||
pytest.param(Op.EXTCALL, [0, 0, 0], id="EXTCALL"), | ||
pytest.param(Op.EXTDELEGATECALL, [0, 0], id="EXTDELEGATECALL"), | ||
pytest.param(Op.EXTSTATICCALL, [0, 0], id="EXTSTATICCALL"), | ||
], | ||
ids=lambda x: x, | ||
) | ||
@pytest.mark.parametrize( | ||
"return_data", | ||
[ | ||
b"", | ||
b"\x10" * 0x10, | ||
b"\x20" * 0x20, | ||
b"\x30" * 0x30, | ||
], | ||
ids=lambda x: "len_%x" % len(x), | ||
) | ||
@pytest.mark.parametrize( | ||
"offset", | ||
[ | ||
0, | ||
0x10, | ||
0x20, | ||
0x30, | ||
], | ||
ids=lambda x: "offset_%x" % x, | ||
) | ||
def test_returndataload_handling( | ||
state_test: StateTestFiller, | ||
opcode: Op, | ||
call_suffix: List[int], | ||
return_data: bytes, | ||
offset: int, | ||
): | ||
""" | ||
Much simpler than returndatacopy, no memory or boosted call. Returner is called | ||
and results are stored in storage slot, which is asserted for expected values. | ||
The parameters offset and return data are configured to test boundary conditions. | ||
""" | ||
env = Environment() | ||
address_entry_point = Address(0x1000000) | ||
address_returner = Address(0x1000001) | ||
tx = Transaction(to=address_entry_point, gas_limit=2_000_000, nonce=1) | ||
|
||
slot_result_start = 0x1000 | ||
|
||
pre = { | ||
TestAddress: Account(balance=10**18, nonce=tx.nonce), | ||
address_entry_point: Account( | ||
nonce=1, | ||
code=Container( | ||
sections=[ | ||
Section.Code( | ||
code=opcode(address_returner, *call_suffix) | ||
+ Op.SSTORE(slot_result_start, Op.RETURNDATALOAD(offset)) | ||
+ Op.SSTORE(slot_code_worked, value_code_worked) | ||
+ Op.STOP, | ||
max_stack_height=len(call_suffix) + 1, | ||
) | ||
] | ||
), | ||
), | ||
address_returner: Account( | ||
nonce=1, | ||
code=Container( | ||
sections=[ | ||
Section.Code( | ||
code=Op.DATACOPY(0, 0, Op.DATASIZE) + Op.RETURN(0, Op.DATASIZE), | ||
max_stack_height=3, | ||
), | ||
Section.Data(data=return_data), | ||
] | ||
), | ||
), | ||
} | ||
|
||
result = [0] * 0x20 | ||
extent = 0x20 - max(0, 0x20 + offset - len(return_data)) | ||
if extent > 0 and len(return_data) > 0: | ||
result[0:extent] = [return_data[0]] * extent | ||
post = { | ||
address_entry_point: Account( | ||
storage={ | ||
slot_code_worked: value_code_worked, | ||
slot_result_start: bytes(result), | ||
} | ||
) | ||
} | ||
|
||
state_test( | ||
env=env, | ||
pre=pre, | ||
tx=tx, | ||
post=post, | ||
) |