From 3ed99a0ee73c2efc139d4d42bd822bc04162808b Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Tue, 23 Apr 2024 15:16:17 -0600 Subject: [PATCH] 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 --- src/ethereum_test_tools/vm/opcode.py | 37 ++- .../__init__.py | 3 + .../eip7676_address_space_extension/spec.py | 5 + .../test_ase_opcodes.py | 219 ++++++++++++++++++ whitelist.txt | 4 +- 5 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 tests/prague/eip7676_address_space_extension/__init__.py create mode 100644 tests/prague/eip7676_address_space_extension/spec.py create mode 100644 tests/prague/eip7676_address_space_extension/test_ase_opcodes.py diff --git a/src/ethereum_test_tools/vm/opcode.py b/src/ethereum_test_tools/vm/opcode.py index 8b503670c3..70270c4035 100644 --- a/src/ethereum_test_tools/vm/opcode.py +++ b/src/ethereum_test_tools/vm/opcode.py @@ -5316,7 +5316,7 @@ class Opcodes(Opcode, Enum): Fork ---- - Prague + EOF Fork Gas ---- @@ -5329,6 +5329,35 @@ class Opcodes(Opcode, Enum): Source: [EIP-7069](https://eips.ethereum.org/EIPS/eip-7069) """ + RETURNDATALOAD = Opcode(0xF7, popped_stack_items=1, pushed_stack_items=1) + """ + RETURNDATALOAD(offset) = returndata + ---- + + Description + ---- + Copies 32 bytes of return data onto the operand stack + + Inputs + ---- + - offset: The offset within the return data to start copying. There must be 32 byes of return + data at that address or an exceptional halt occurs. + + Outputs + ---- + - returndata: the 32 bytes of return data at the offset + + Fork + ---- + EOF Fork + + Gas + ---- + 3 + + Source: [EIP-7069](https://eips.ethereum.org/EIPS/eip-7069) + """ + EXTDELEGATECALL = Opcode(0xF9, popped_stack_items=3, pushed_stack_items=1) """ EXTDELEGATECALL(target_address, input_offset, input_size) = address @@ -5354,7 +5383,7 @@ class Opcodes(Opcode, Enum): Fork ---- - Prague + EOF Fork Gas ---- @@ -5400,7 +5429,7 @@ class Opcodes(Opcode, Enum): Source: [evm.codes/#FA](https://www.evm.codes/#FA) """ - EXTSTATICCALL = Opcode(0xFB, popped_stack_items=4, pushed_stack_items=1) + EXTSTATICCALL = Opcode(0xFB, popped_stack_items=3, pushed_stack_items=1) """ EXTSTATICCALL(target_address, input_offset, input_size) = address ---- @@ -5424,7 +5453,7 @@ class Opcodes(Opcode, Enum): Fork ---- - Prague + EOF Fork Gas ---- diff --git a/tests/prague/eip7676_address_space_extension/__init__.py b/tests/prague/eip7676_address_space_extension/__init__.py new file mode 100644 index 0000000000..094865f879 --- /dev/null +++ b/tests/prague/eip7676_address_space_extension/__init__.py @@ -0,0 +1,3 @@ +""" +Address Space Extension Tests +""" diff --git a/tests/prague/eip7676_address_space_extension/spec.py b/tests/prague/eip7676_address_space_extension/spec.py new file mode 100644 index 0000000000..7bf760554f --- /dev/null +++ b/tests/prague/eip7676_address_space_extension/spec.py @@ -0,0 +1,5 @@ +""" +EOF V1 Constants used throughout all tests +""" + +EOF_FORK_NAME = "Prague" diff --git a/tests/prague/eip7676_address_space_extension/test_ase_opcodes.py b/tests/prague/eip7676_address_space_extension/test_ase_opcodes.py new file mode 100644 index 0000000000..573e1db9e8 --- /dev/null +++ b/tests/prague/eip7676_address_space_extension/test_ase_opcodes.py @@ -0,0 +1,219 @@ +""" +Execution of CALLF, RETF opcodes within EOF V1 containers tests +""" + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Environment, + StateTestFiller, + TestAddress, + Transaction, +) +from ethereum_test_tools.common.conversions import BytesConvertible, to_bytes +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 .spec import EOF_FORK_NAME + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7676.md" +REFERENCE_SPEC_VERSION = ( + "00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +) + +pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) + + +@pytest.mark.parametrize( + "target_address", + ( + b"", + b"\x10\x00", + b"\x78" * 20, + b"\xff" * 20, + b"\x01" + (b"\x00" * 20), + b"\x5a" * 28, + b"\x5a" * 32, + b"\xff" * 32, + ), + ids=["zero", "short", "mid20", "max20", "minAse", "midAse", "fullAse", "maxAse"], +) +@pytest.mark.parametrize("target_account_type", ("empty", "EOA", "Contract"), ids=lambda x: x) +def test_address_space_extension( + state_test: StateTestFiller, + target_address: BytesConvertible, + target_account_type: str, +): + """ + Test contacts with possibly extended address and fail if address is too large + """ + env = Environment() + + address_bytes = to_bytes(target_address) + ase_address = len(address_bytes) > 20 + if ase_address and address_bytes[0] == b"00": + raise ValueError("Test instrumentation requires target addresses trim leading zeros") + + pre = { + TestAddress: Account( + balance=1000000000000000000000, + nonce=1, + ), + Address(0x100): Account( + code=( + Op.MSTORE(0, Op.PUSH32(address_bytes)) + + Op.SSTORE(0, Op.CALL(50000, 0x200, 0, 0, 32, 0, 0)) + + Op.SSTORE(1, Op.CALL(50000, 0x300, 0, 0, 32, 0, 0)) + + Op.SSTORE(2, Op.CALL(50000, 0x400, 0, 0, 32, 0, 0)) + + Op.SSTORE(3, Op.CALL(50000, 0x500, 0, 0, 32, 0, 0)) + + Op.SSTORE(4, Op.CALL(50000, 0x201, 0, 0, 32, 0, 0)) + + Op.SSTORE(5, Op.CALL(50000, 0x301, 0, 0, 32, 0, 0)) + + Op.SSTORE(6, Op.CALL(50000, 0x401, 0, 0, 32, 0, 0)) + + Op.SSTORE(7, Op.CALL(50000, 0x501, 0, 0, 32, 0, 0)) + + Op.SSTORE(8, Op.CALL(50000, 0x601, 0, 0, 32, 0, 0)) + + Op.STOP() + ), + nonce=1, + ), + Address(0x200): Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(0, Op.BALANCE(Op.CALLDATALOAD(0))) + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=2, + ) + ], + ), + nonce=1, + ), + Address(0x201): Account( + code=Op.SSTORE(0, Op.BALANCE(Op.CALLDATALOAD(0))) + Op.STOP, + nonce=1, + ), + Address(0x300): Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(0, Op.EXTCALL(Op.CALLDATALOAD(0), 0, 0, 0)) + + Op.SSTORE(1, Op.RETURNDATALOAD(0)) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=4, + ) + ], + ), + nonce=1, + ), + Address(0x301): Account( + code=Op.SSTORE(0, Op.CALL(Op.GAS, Op.CALLDATALOAD(0), 0, 0, 0, 0, 0)) + + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) + + Op.SSTORE(1, Op.MLOAD(0)) + + Op.STOP, + nonce=1, + ), + Address(0x400): Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(0, Op.EXTDELEGATECALL(Op.CALLDATALOAD(0), 0, 0)) + + Op.SSTORE(1, Op.RETURNDATALOAD(0)) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ) + ], + ), + nonce=1, + ), + Address(0x401): Account( + code=Op.SSTORE(0, Op.DELEGATECALL(Op.GAS, Op.CALLDATALOAD(0), 0, 0, 0, 0)) + + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) + + Op.SSTORE(1, Op.MLOAD(0)) + + Op.STOP, + nonce=1, + ), + Address(0x500): Account( + code=Container( + sections=[ + Section.Code( + code=Op.SSTORE(0, Op.EXTSTATICCALL(Op.CALLDATALOAD(0), 0, 0)) + + Op.SSTORE(1, Op.RETURNDATALOAD(0)) + + Op.STOP, + code_inputs=0, + code_outputs=NON_RETURNING_SECTION, + max_stack_height=3, + ) + ], + ), + nonce=1, + ), + Address(0x501): Account( + code=Op.SSTORE(0, Op.STATICCALL(Op.GAS, Op.CALLDATALOAD(0), 0, 0, 0, 0)) + + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) + + Op.SSTORE(1, Op.MLOAD(0)) + + Op.STOP, + nonce=1, + ), + Address(0x601): Account( + code=Op.SSTORE(0, Op.CALLCODE(Op.GAS, Op.CALLDATALOAD(0), 0, 0, 0, 0, 0)) + + Op.RETURNDATACOPY(0, 0, Op.RETURNDATASIZE) + + Op.SSTORE(1, Op.MLOAD(0)) + + Op.STOP, + nonce=1, + ), + } + + stripped_address = address_bytes[-20:] if ase_address else target_address + post = { + Address(0x100): Account( + storage={0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1} + if not ase_address + else {4: 1, 5: 1, 6: 1, 7: 1, 8: 1} + ) + } + match target_account_type: + case "empty": + # add no account + pass + case "EOA": + pre[Address(stripped_address)] = Account(code="", balance=10**18, nonce=9) + post[Address(0x200)] = Account(storage={} if ase_address else {0: 10**18}) + post[Address(0x201)] = Account(storage={0: 10**18}) + case "Contract": + pre[Address(stripped_address)] = Account( + code=Op.MSTORE(0, Op.ADDRESS) + Op.RETURN(0, 32), balance=0, nonce=0 + ) + # For EOF variants the EXT*CALL reverts, so no storage updates for ASE address + post[Address(0x300)] = Account(storage={} if ase_address else {1: stripped_address}) + post[Address(0x301)] = Account(storage={0: 1, 1: stripped_address}) + # EXTDELEGATECALL fails when calling legacy, so no stripped address + post[Address(0x400)] = Account(storage={} if ase_address else {0: 1}) + post[Address(0x401)] = Account(storage={0: 1, 1: 0x401}) + post[Address(0x500)] = Account(storage={} if ase_address else {1: stripped_address}) + post[Address(0x501)] = Account(storage={0: 1, 1: stripped_address}) + post[Address(0x601)] = Account(storage={0: 1, 1: 0x601}) + case _: + raise ValueError("Unknown account type: " + target_account_type) + + tx = Transaction( + nonce=1, + to=Address(0x100), + gas_limit=50000000, + gas_price=10, + protected=False, + data="", + ) + + state_test( + env=env, + pre=pre, + post=post, + tx=tx, + ) diff --git a/whitelist.txt b/whitelist.txt index c06e368730..a74692507b 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -3,6 +3,7 @@ acl addr address address2 +ase alloc api apis @@ -244,6 +245,7 @@ repo repo's repos returndatacopy +returndataload returndatasize returncontract rlp @@ -272,7 +274,7 @@ StateTestFiller staticcalled stExample str -streetsidesoftware +streetsidesoftiware subcall subclasscheck subdirectories