Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion lists/evm/pending-opcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ next or subsequent hard fork.
| 0x5C | TLOAD | Transient data load | [EIP-1153](https://eips.ethereum.org/EIPS/eip-1153) |
| 0x5D | TSTORE | Transient data store | [EIP-1153](https://eips.ethereum.org/EIPS/eip-1153) |
| 0x5E | MCOPY | Memory copy | [EIP-5656](https://eips.ethereum.org/EIPS/eip-5656) |
| 0xAE | EXTENSION | Extension prefix for non-Ethereum-L1 EVM chains | [EIP-8163](https://eips.ethereum.org/EIPS/eip-8163) |
| 0xD0 | DATALOAD | Loads data from EOF data section, via stack | [EIP-7480](https://eips.ethereum.org/EIPS/eip-7480) |
| 0xD1 | DATALOADN | Loads data from EOF data section, via immediate | [EIP-7480](https://eips.ethereum.org/EIPS/eip-7480) |
| 0xD2 | DATASIZE | Size of the EOF data section | [EIP-7480](https://eips.ethereum.org/EIPS/eip-7480) |
Expand Down Expand Up @@ -222,7 +223,7 @@ scheduled or accepted) are in *italics*.
| 0xAB | | | | |
| 0xAC | | | | |
| 0xAD | | | | |
| 0xAE | | | | |
| *0xAE* | *EXTENSION* | *System* | *Bogota* | *[EIP-8163](https://eips.ethereum.org/EIPS/eip-8163)* |
| 0xAF | | | | |
| 0xB0 | | | | |
| 0xB1 | | | | |
Expand Down
1 change: 1 addition & 0 deletions lists/evm/proposed-opcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,4 @@ unshipped EIPs, even withdrawn and non-viable proposals.
| [EIP-7620](https://eips.ethereum.org/EIPS/eip-7620) | 0xEC | EOFCREATE | Create from EOF contained initcode |
| [EIP-7620](https://eips.ethereum.org/EIPS/eip-7620) | 0xED | TXCREATE | Create from transaction contained initcode (removed from EIP-7620) |
| [EIP-7620](https://eips.ethereum.org/EIPS/eip-7620) | 0xEE | RETURNCONTRACT | Contract to be created, references EOF data |
| [EIP-8163](https://eips.ethereum.org/EIPS/eip-8163) | 0xAE | EXTENSION | Extension prefix for non-Ethereum-L1 EVM chains |
1 change: 1 addition & 0 deletions packages/testing/src/execution_testing/cli/evm_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Op.STOP,
Op.REVERT,
Op.INVALID,
Op.EXTENSION,
Op.JUMP,
Op.JUMPI,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def test_individual_opcodes(opcode: Op) -> None:
else:
expected_output = f"Op.{opcode._name_}"

bytecode = opcode.int().to_bytes(1, byteorder="big") + data_portion
bytecode = bytes(opcode) + data_portion
assert process_evm_bytes_string("0x" + bytecode.hex()) == expected_output


Expand Down
1 change: 1 addition & 0 deletions packages/testing/src/execution_testing/forks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
"ArrowGlacier",
"Berlin",
"BerlinToLondonAt5",
"Bogota",
"Byzantium",
"Constantinople",
"ConstantinopleFix",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
EIP-8163: Reserve EXTENSION (0xae) opcode.

Reserve `EXTENSION` as an extension prefix for EVM chains other than
Ethereum L1, where it behaves exactly like `INVALID`.

https://eips.ethereum.org/EIPS/eip-8163
"""

from typing import Callable, Dict

from execution_testing.vm import OpcodeBase, Opcodes

from ....base_fork import BaseFork


class EIP8163(BaseFork):
"""EIP-8163 class."""

@classmethod
def opcode_gas_map(
cls,
) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]:
"""Add EXTENSION, which halts exceptionally like INVALID."""
base_map = super(EIP8163, cls).opcode_gas_map()
return {
**base_map,
Opcodes.EXTENSION: 0,
}
29 changes: 29 additions & 0 deletions packages/testing/src/execution_testing/vm/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5274,6 +5274,35 @@ class Opcodes(Opcode, Enum):
Source: [evm.codes/#A4](https://www.evm.codes/#A4)
"""

EXTENSION = Opcode(0xAE, terminating=True)
"""
EXTENSION()
----

Description
----
Instruction reserved as an extension prefix for non-Ethereum-L1 EVM
chains; behaves exactly like INVALID on Ethereum L1

Inputs
----
None

Outputs
----
None

Fork
----
Bogota

Gas
----
All the remaining gas in this context is consumed

Source: [EIP-8163](https://eips.ethereum.org/EIPS/eip-8163)
"""

DUPN = Opcode(
0xE6,
pushed_stack_items=1,
Expand Down
1 change: 1 addition & 0 deletions tests/bogota/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Test cases for EVM functionality introduced in Bogota."""
1 change: 1 addition & 0 deletions tests/bogota/eip8163_extension/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for [EIP-8163: Reserve EXTENSION (0xae) opcode](https://eips.ethereum.org/EIPS/eip-8163)."""
17 changes: 17 additions & 0 deletions tests/bogota/eip8163_extension/spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Reference spec for [EIP-8163: Reserve EXTENSION (0xae) opcode](https://eips.ethereum.org/EIPS/eip-8163)."""

from dataclasses import dataclass


@dataclass(frozen=True)
class ReferenceSpec:
"""Reference specification."""

git_path: str
version: str


ref_spec_8163 = ReferenceSpec(
git_path="EIPS/eip-8163.md",
version="aef32ae28e42ba3a391ed8d991ddf6a68d53c152",
)
195 changes: 195 additions & 0 deletions tests/bogota/eip8163_extension/test_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
"""
Tests for [EIP-8163: Reserve EXTENSION (0xae) opcode](https://eips.ethereum.org/EIPS/eip-8163).

EXTENSION behaves exactly like INVALID on chains with no extensions
defined for it, Ethereum L1 included, and the byte is neutral to
JUMPDEST analysis everywhere. All assertions here hold on any
EIP-8163-conformant EVM, including chains that define extensions.

These tests do not assert the non-existence of extensions that would
be conformant but are undefined today: there is no way to
forward-guess under what conditions such an extension would succeed,
and no point in testing it.
"""

import pytest
from execution_testing import (
Account,
Alloc,
Bytecode,
Fork,
Op,
StateTestFiller,
Transaction,
TransactionReceipt,
)

from .spec import ref_spec_8163

REFERENCE_SPEC_GIT_PATH = ref_spec_8163.git_path
REFERENCE_SPEC_VERSION = ref_spec_8163.version

pytestmark = pytest.mark.valid_from("EIP8163")

slot_code_worked = 1
value_code_worked = 0x1234
value_code_untouched = 0xBA5E


@pytest.mark.parametrize(
"opcode,success,all_gas_consumed",
[
pytest.param(Op.EXTENSION, False, True),
pytest.param(Op.INVALID, False, True),
pytest.param(Op.REVERT(0, 0), False, False, id="REVERT"),
pytest.param(Op.JUMPDEST, True, False),
],
)
def test_top_level_call(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
opcode: Bytecode,
success: bool,
all_gas_consumed: bool,
) -> None:
"""
Call a contract whose whole code is the single tested byte.

EXTENSION behaves as INVALID, JUMPDEST as sanity check.
"""
contract_address = pre.deploy_contract(code=opcode)

# One gas more than the code needs, so a normal halt refunds exactly
# that gas while an exceptional halt consumes it with the rest.
execution_gas = (
fork.transaction_intrinsic_cost_calculator()() + opcode.gas_cost(fork)
)
gas_limit = execution_gas + 1
tx = Transaction(
gas_limit=gas_limit,
to=contract_address,
sender=pre.fund_eoa(),
expected_receipt=TransactionReceipt(
status=int(success),
cumulative_gas_used=(
gas_limit if all_gas_consumed else execution_gas
),
),
)

state_test(pre=pre, post={}, tx=tx)


@pytest.mark.parametrize(
"target,valid_jump",
[
pytest.param(Op.JUMPDEST, True),
pytest.param(Op.PUSH1(0x5B), False, id="push_data_0x5b"),
],
)
@pytest.mark.parametrize(
"following",
[
pytest.param(Bytecode(), id="nothing"),
Op.STOP,
Op.ADD,
Op.JUMPDEST,
pytest.param(Op.PUSH1(0), id="PUSH1"),
pytest.param(Op.PUSH32(0), id="PUSH32"),
Op.EXTENSION,
Op.SELFDESTRUCT,
],
)
def test_jumpdest_analysis_neutrality(
Comment thread
pdobacz marked this conversation as resolved.
state_test: StateTestFiller,
pre: Alloc,
following: Bytecode,
target: Bytecode,
valid_jump: bool,
) -> None:
"""
Jump over an EXTENSION byte to a destination right behind it.

JUMPDEST analysis ignores EXTENSION: a JUMPDEST behind EXTENSION
is a valid destination and a 0x5b held as PUSH1 data behind
EXTENSION is not. EXTENSION is never executed. Only JUMPDEST, the
PUSH opcodes and EXTENSION itself matter to the analysis of the
byte behind EXTENSION; every other byte is data to it.
"""
code = (
Op.SSTORE(slot_code_worked, value_code_worked)
+ Op.JUMP(pc=Op.PUSH2(data_placeholder="destination"))
+ Op.EXTENSION
+ following
+ target
)
code.substitute(destination=len(code) - 1)

contract_address = pre.deploy_contract(
code=code,
storage={slot_code_worked: value_code_untouched},
)

tx = Transaction(to=contract_address, sender=pre.fund_eoa())

value = value_code_worked if valid_jump else value_code_untouched
state_test(
pre=pre,
post={contract_address: Account(storage={slot_code_worked: value})},
tx=tx,
)


@pytest.mark.parametrize(
"opcode,success",
[
pytest.param(Op.EXTENSION, False),
pytest.param(Op.INVALID, False),
pytest.param(Op.JUMPDEST, True),
],
)
@pytest.mark.parametrize(
"following",
[
pytest.param(bytes([b]), id=f"0x{b:02x}")
for b in [0x5B, *range(0x60, 0x80)]
]
+ [pytest.param(b"", id="nothing")],
)
def test_solo_extension_bytes(
Comment thread
pdobacz marked this conversation as resolved.
state_test: StateTestFiller,
pre: Alloc,
opcode: Op,
success: bool,
following: bytes,
) -> None:
"""
Execute the tested byte followed by a solo 0x5b or 0x60..0x7f
byte (or nothing), expect it to halt exceptionally always.

EIP-8163 rules these out as single-byte extension immediates.
The stack is filled first, so the halt is known not to require an
empty stack.

EXTENSION behaves as INVALID, JUMPDEST as sanity check.
"""
code = (
Op.SSTORE(slot_code_worked, value_code_worked)
+ Op.PUSH1(1) * 256
+ opcode
+ following
)
contract_address = pre.deploy_contract(
code=code,
storage={slot_code_worked: value_code_untouched},
)

tx = Transaction(to=contract_address, sender=pre.fund_eoa())

value = value_code_worked if success else value_code_untouched
state_test(
pre=pre,
post={contract_address: Account(storage={slot_code_worked: value})},
tx=tx,
)
Loading