Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test EIP-7069 Address Space Extension implications #522

Merged
merged 18 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
85 changes: 57 additions & 28 deletions src/ethereum_test_tools/vm/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -5038,6 +5038,30 @@ class Opcodes(Opcode, Enum):

"""

TXCREATE = Opcode(0xED, popped_stack_items=5, pushed_stack_items=1)
"""
!!! Note: This opcode is under development

TXCREATE(tx_initcode_hash, value, salt, input_offset, input_size)
----

Description
----

Inputs
----

Outputs
----

Fork
----

Gas
----

"""

RETURNCONTRACT = Opcode(
0xEE, popped_stack_items=2, pushed_stack_items=1, data_portion_length=1
)
Expand Down Expand Up @@ -5316,7 +5340,7 @@ class Opcodes(Opcode, Enum):

Fork
----
Prague
EOF Fork

Gas
----
Expand All @@ -5329,6 +5353,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
Expand All @@ -5354,7 +5407,7 @@ class Opcodes(Opcode, Enum):

Fork
----
Prague
EOF Fork

Gas
----
Expand Down Expand Up @@ -5400,7 +5453,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
----
Expand All @@ -5424,7 +5477,7 @@ class Opcodes(Opcode, Enum):

Fork
----
Prague
EOF Fork

Gas
----
Expand All @@ -5434,30 +5487,6 @@ class Opcodes(Opcode, Enum):
Source: [EIP-7069](https://eips.ethereum.org/EIPS/eip-7069)
"""

CREATE4 = Opcode(0xF7, popped_stack_items=5, pushed_stack_items=1)
"""
!!! Note: This opcode is under development

CREATE4()
----

Description
----

Inputs
----

Outputs
----

Fork
----

Gas
----

"""

REVERT = Opcode(0xFD, popped_stack_items=2)
"""
REVERT(offset, size)
Expand Down
3 changes: 3 additions & 0 deletions tests/prague/eip7692_eof_v1/eip7069_extcall/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Revamped Call Instructions Tests
"""
5 changes: 5 additions & 0 deletions tests/prague/eip7692_eof_v1/eip7069_extcall/spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
EOF V1 Constants used throughout all tests
"""

EOF_FORK_NAME = "Prague"
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
"""
Tests the "Address Space Extension" aspect of EXT*CALL
"""
import itertools

import pytest

from ethereum_test_tools import (
Account,
Address,
Environment,
StateTestFiller,
TestAddress,
Transaction,
)
from ethereum_test_tools.common.conversions import BytesConvertible, to_bytes
shemnon marked this conversation as resolved.
Show resolved Hide resolved
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
shemnon marked this conversation as resolved.
Show resolved Hide resolved

REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7069.md"
REFERENCE_SPEC_VERSION = "1795943aeacc86131d5ab6bb3d65824b3b1d4cad"

pytestmark = pytest.mark.valid_from(EOF_FORK_NAME)

_call_type = itertools.count(1)
ID_EXTCALL = next(_call_type)
ID_CALL = next(_call_type)
ID_EXTDELEGATECALL = next(_call_type)
ID_DELEGATECALL = next(_call_type)
ID_EXTSTATICCALL = next(_call_type)
ID_STATICCALL = next(_call_type)
ID_CALLCODE = next(_call_type)

_address_allocation = itertools.count(100000000)
ADDRESS_EXTCALL = Address(next(_address_allocation))
ADDRESS_CALL = Address(next(_address_allocation))
ADDRESS_EXTDELEGATECALL = Address(next(_address_allocation))
ADDRESS_DELEGATECALL = Address(next(_address_allocation))
ADDRESS_EXTSTATICCALL = Address(next(_address_allocation))
ADDRESS_STATICCALL = Address(next(_address_allocation))
ADDRESS_CALLCODE = Address(next(_address_allocation))


@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"],
)
shemnon marked this conversation as resolved.
Show resolved Hide resolved
@pytest.mark.parametrize("target_account_type", ("empty", "EOA", "Contract"), ids=lambda x: x)
def test_address_space_extension(
state_test: StateTestFiller,
target_address: BytesConvertible,
shemnon marked this conversation as resolved.
Show resolved Hide resolved
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)
shemnon marked this conversation as resolved.
Show resolved Hide resolved
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(ID_EXTCALL, Op.CALL(50000, ADDRESS_EXTCALL, 0, 0, 32, 0, 0))
+ Op.SSTORE(ID_CALL, Op.CALL(50000, ADDRESS_CALL, 0, 0, 32, 0, 0))
+ Op.SSTORE(
ID_EXTDELEGATECALL, Op.CALL(50000, ADDRESS_EXTDELEGATECALL, 0, 0, 32, 0, 0)
)
+ Op.SSTORE(ID_DELEGATECALL, Op.CALL(50000, ADDRESS_DELEGATECALL, 0, 0, 32, 0, 0))
+ Op.SSTORE(
ID_EXTSTATICCALL, Op.CALL(50000, ADDRESS_EXTSTATICCALL, 0, 0, 32, 0, 0)
)
+ Op.SSTORE(ID_STATICCALL, Op.CALL(50000, ADDRESS_STATICCALL, 0, 0, 32, 0, 0))
+ Op.SSTORE(ID_CALLCODE, Op.CALL(50000, ADDRESS_CALLCODE, 0, 0, 32, 0, 0))
+ Op.STOP()
),
nonce=1,
shemnon marked this conversation as resolved.
Show resolved Hide resolved
),
ADDRESS_EXTCALL: 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_CALL: 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_EXTDELEGATECALL: 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_DELEGATECALL: 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_EXTSTATICCALL: 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_STATICCALL: 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_CALLCODE: 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,
shemnon marked this conversation as resolved.
Show resolved Hide resolved
),
}

stripped_address = address_bytes[-20:] if ase_address else target_address
post = {
Address(0x100): Account(
storage={
ID_CALL: 1,
ID_DELEGATECALL: 1,
ID_STATICCALL: 1,
ID_CALLCODE: 1,
}
if ase_address
else {
ID_EXTCALL: 1,
ID_CALL: 1,
ID_EXTDELEGATECALL: 1,
ID_DELEGATECALL: 1,
ID_EXTSTATICCALL: 1,
ID_STATICCALL: 1,
ID_CALLCODE: 1,
}
)
}
match target_account_type:
case "empty":
# add no account
pass
case "EOA":
pre[Address(stripped_address)] = Account(code="", balance=10**18, nonce=9)
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_EXTCALL] = Account(storage={} if ase_address else {1: stripped_address})
post[ADDRESS_CALL] = Account(storage={0: 1, 1: stripped_address})
# EXTDELEGATECALL fails when calling legacy, so no stripped address
post[ADDRESS_EXTDELEGATECALL] = Account(storage={} if ase_address else {0: 1})
post[ADDRESS_DELEGATECALL] = Account(storage={0: 1, 1: ADDRESS_DELEGATECALL})
post[ADDRESS_EXTSTATICCALL] = Account(
storage={} if ase_address else {1: stripped_address}
)
post[ADDRESS_STATICCALL] = Account(storage={0: 1, 1: stripped_address})
post[ADDRESS_CALLCODE] = Account(storage={0: 1, 1: ADDRESS_CALLCODE})
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,
)
Loading