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

EIP-1153 Transient Storage Tests #230

Merged
merged 28 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8538212
tools: add tstore and tload opcodes
danceratopz Jul 27, 2023
89621b5
tests: add simple execution context tests for tstore and tload
danceratopz Jul 27, 2023
1193409
tests: add spec and conftest for transient storage tests
danceratopz Jul 27, 2023
fce12be
tests: test simple transient storage usage in initcode
danceratopz Jul 27, 2023
d81ac39
tests: add more contract creation tstorage tests
danceratopz Sep 1, 2023
feb4923
tests: add reentrancy tests for tload and tstore
danceratopz Sep 5, 2023
666f9cb
tests: additionally test CALL's return value
danceratopz Sep 5, 2023
d22a1c4
tests: rename create contexts test module
danceratopz Sep 5, 2023
7bae4a3
tests: minor test execution test improvement
danceratopz Sep 5, 2023
14849e9
tests: rename execution contexts test module
danceratopz Sep 5, 2023
abeb260
tests: add simple tstorage tests
danceratopz Sep 5, 2023
ed72dfc
tests: fix revert_undoes_tstorage_after_successful_cal w/mario
danceratopz Sep 5, 2023
98537eb
tests: remove unused test parameters; mark valid from Cancun
danceratopz Sep 5, 2023
0c86456
tests: clean-up docstrinsgs; no change to tests
danceratopz Sep 5, 2023
cfb13d8
tests: slight improvement to call execution context test
danceratopz Sep 5, 2023
990c449
tests: fix (very basic) staticcall tests
danceratopz Sep 5, 2023
cb12d90
tests: improve delegatecall; add delegatecall with revert test
danceratopz Sep 5, 2023
aa6175a
tests: fix/improve callcode tests
danceratopz Sep 5, 2023
8b4843f
tests: mark execution tests valid from Cancun; clean-up ids
danceratopz Sep 5, 2023
662b1a5
refactor: define pytest_param properly in test case enum
danceratopz Sep 5, 2023
32a0b1b
clean-up: remove unused pytest_param entries in test cases
danceratopz Sep 5, 2023
f393ed8
tests: add create testcase w/o tload in constructor code
danceratopz Sep 7, 2023
fc5e856
refactor: rewrite test cases using a more generic enum class
danceratopz Sep 7, 2023
e7cb214
tests: add {DELEGATECALL,CODECALL}_WITH_INVALID sub-call tests
danceratopz Sep 7, 2023
4398567
clean-up: remove unwanted printf
danceratopz Sep 7, 2023
530b589
tests: add INVALID reentrancy tests
danceratopz Sep 7, 2023
2b95376
tests: explicitly test staticall tstore w/underflow
danceratopz Sep 7, 2023
80243fb
tests: add oog execution tests; improve PytestParameterEnum (#24)
marioevz Sep 7, 2023
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
9 changes: 5 additions & 4 deletions src/ethereum_test_tools/vm/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,8 @@ class Opcodes(Opcode, Enum):
MSIZE = Opcode(0x59, pushed_stack_items=1)
GAS = Opcode(0x5A, pushed_stack_items=1)
JUMPDEST = Opcode(0x5B)
RJUMP = Opcode(0x5C, data_portion_length=2)
RJUMPI = Opcode(0x5D, popped_stack_items=1, data_portion_length=2)
TLOAD = Opcode(0x5C, popped_stack_items=1, pushed_stack_items=1)
TSTORE = Opcode(0x5D, popped_stack_items=2)
MCOPY = Opcode(0x5E, popped_stack_items=3)
RETF = Opcode(0x49)

Expand Down Expand Up @@ -341,8 +341,9 @@ class Opcodes(Opcode, Enum):
LOG3 = Opcode(0xA3, popped_stack_items=5)
LOG4 = Opcode(0xA4, popped_stack_items=6)

TLOAD = Opcode(0xB3, popped_stack_items=1, pushed_stack_items=1)
TSTORE = Opcode(0xB4, popped_stack_items=2)
RJUMP = Opcode(0xE0, data_portion_length=2)
RJUMPI = Opcode(0xE1, popped_stack_items=1, data_portion_length=2)
RJUMPV = Opcode(0xE2)

CREATE = Opcode(0xF0, popped_stack_items=3, pushed_stack_items=1)
CALL = Opcode(0xF1, popped_stack_items=7, pushed_stack_items=1)
Expand Down
120 changes: 120 additions & 0 deletions tests/cancun/eip1153_tstore/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
EIP-1153 Tests
"""

from enum import Enum, unique
from pprint import pprint
from typing import List

import pytest

from ethereum_test_tools import Opcodes as Op


class PytestParameterEnum(Enum):
"""
Helper class for defining Pytest parameters used in test cases.

This class helps define enum `value`s as `pytest.param` objects that then can
be used to create a parametrize decorator that can be applied to tests,
for example,

```python
@TStorageCallContextTestCases.parametrize()
def test_function(test_value):
pass
```

Classes which derive from this class must define each test case as a different enum
field with a dictionary as value.

The dictionary must contain:
i. A `description` key with a string value describing the test case.
ii. (Optional) A `pytest_marks` key with a single mark or list of pytest
marks to apply to the test case. For example,

```
pytest_marks=pytest.mark.xfail
```
or

```
pytest_marks=[pytest.mark.xfail, pytest.mark.skipif]
```
iii. (Optional) An `id` key with the name of the test.

The rest of the keys in the dictionary are the parameters of the test case.

The test case ID is set as the enum name converted to lowercase.
"""

def __init__(self, value):
assert isinstance(value, dict)
assert "description" in value
self._value_ = value

def param(self, names: List[str]):
"""
Return the `pytest.param` value for this test case.
"""
value = self._value_
if "pytest_marks" in value:
marioevz marked this conversation as resolved.
Show resolved Hide resolved
marks = {"marks": value["pytest_marks"]}
else:
marks = {}
if "pytest_id" in value:
id = value["pytest_id"]
else:
id = self.name.lower()
return pytest.param(*[value[name] for name in names], id=id, **marks)

@classmethod
def special_keywords(cls) -> List[str]:
"""
Return the special dictionary keywords that are not test parameters.
"""
return ["description", "pytest_marks", "pytest_id"]

def names(self) -> List[str]:
"""
Return the names of all the parameters included in the enum value dict.
"""
return sorted([k for k in self._value_.keys() if k not in self.special_keywords()])

@property
def description(self):
"""
Returns the description of this test case.
"""
return self._value_["description"]

@classmethod
def parametrize(cls):
"""
Returns the decorator to parametrize a test with this enum.
"""
names = None
for test_case_names in [test_case.names() for test_case in cls]:
if names is None:
names = test_case_names
else:
if set(names) != set(test_case_names):
pprint(names)
pprint(test_case_names)
assert set(names) == set(
test_case_names
), "All test cases must have the same parameter names."
assert names is not None, "Enum must have at least one test case."

return pytest.mark.parametrize(names, [test_case.param(names) for test_case in cls])


@unique
class CreateOpcodeParams(PytestParameterEnum):
"""
Helper enum class to parametrize tests with different contract creation
opcodes: CREATE and CREATE2.
"""

CREATE = {"opcode": Op.CREATE, "description": "Test CREATE opcode."}
CREATE2 = {"opcode": Op.CREATE2, "description": "Test CREATE2 opcode."}
14 changes: 14 additions & 0 deletions tests/cancun/eip1153_tstore/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Pytest plugin local to EIP-1153 tests.
"""
from typing import List

import pytest


@pytest.fixture(autouse=True)
def eips(eip_enabled: bool = True) -> List[int]:
"""
Returns a list of EIPs to enable in the client t8n tool.
"""
return [1153] if eip_enabled else []
30 changes: 30 additions & 0 deletions tests/cancun/eip1153_tstore/spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Defines EIP-1153 specification constants and functions.
"""
from dataclasses import dataclass


@dataclass(frozen=True)
class ReferenceSpec:
"""
Defines the reference spec version and git path.
"""

git_path: str
version: str


ref_spec_1153 = ReferenceSpec("EIPS/eip-1153.md", "6f0be621c76a05a7b3aaf0e9297afd425c26e9d0")


@dataclass(frozen=True)
class Spec:
"""
Parameters from the EIP-1153 specifications as defined at
https://eips.ethereum.org/EIPS/eip-1153
"""

TLOAD_OPCODE_BYTE = 0x5C
TSTORE_OPCODE_BYTE = 0x5D
TLOAD_GAS_COST = 100
TSTORE_GAS_COST = 100
141 changes: 141 additions & 0 deletions tests/cancun/eip1153_tstore/test_tstorage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""
abstract: Tests [EIP-1153: Transient Storage Opcodes](https://eips.ethereum.org/EIPS/eip-1153)

Test [EIP-1153: Transient Storage Opcodes](https://eips.ethereum.org/EIPS/eip-1153). Ports
and extends some tests from
[ethereum/tests/src/EIPTestsFiller/StateTests/stEIP1153-transientStorage/](https://github.com/ethereum/tests/blob/9b00b68593f5869eb51a6659e1cc983e875e616b/src/EIPTestsFiller/StateTests/stEIP1153-transientStorage)
""" # noqa: E501

# from typing import Mapping

import pytest

from ethereum_test_tools import Account, Environment
from ethereum_test_tools import Opcodes as Op
from ethereum_test_tools import StateTestFiller, TestAddress, Transaction

from .spec import ref_spec_1153

REFERENCE_SPEC_GIT_PATH = ref_spec_1153.git_path
REFERENCE_SPEC_VERSION = ref_spec_1153.version

pytestmark = [pytest.mark.valid_from("Cancun")]

code_address = 0x100


def test_transient_storage_unset_values(state_test: StateTestFiller):
"""
Test that tload returns zero for unset values. Loading an arbitrary value is
0 at beginning of transaction: TLOAD(x) is 0.

Based on [ethereum/tests/.../01_tloadBeginningTxnFiller.yml](https://github.com/ethereum/tests/blob/9b00b68593f5869eb51a6659e1cc983e875e616b/src/EIPTestsFiller/StateTests/stEIP1153-transientStorage/01_tloadBeginningTxnFiller.yml)", # noqa: E501
"""
env = Environment()

slots_under_test = [0, 1, 2, 2**128, 2**256 - 1]
code = b"".join([Op.SSTORE(slot, Op.TLOAD(slot)) for slot in slots_under_test])

pre = {
TestAddress: Account(balance=10_000_000),
code_address: Account(code=code, storage={slot: 1 for slot in slots_under_test}),
}

txs = [
Transaction(
to=code_address,
data=b"",
gas_limit=1_000_000,
)
]

post = {code_address: Account(storage={slot: 0 for slot in slots_under_test})}

state_test(
env=env,
pre=pre,
post=post,
txs=txs,
)


def test_tload_after_tstore(state_test: StateTestFiller):
"""
Loading after storing returns the stored value: TSTORE(x, y), TLOAD(x)
returns y.

Based on [ethereum/tests/.../02_tloadAfterTstoreFiller.yml](https://github.com/ethereum/tests/blob/9b00b68593f5869eb51a6659e1cc983e875e616b/src/EIPTestsFiller/StateTests/stEIP1153-transientStorage/02_tloadAfterTstoreFiller.yml)", # noqa: E501
"""
env = Environment()

slots_under_test = [0, 1, 2, 2**128, 2**256 - 1]
code = b"".join(
[Op.TSTORE(slot, slot) + Op.SSTORE(slot, Op.TLOAD(slot)) for slot in slots_under_test]
)

pre = {
TestAddress: Account(balance=10_000_000),
code_address: Account(code=code, storage={slot: 0 for slot in slots_under_test}),
}

txs = [
Transaction(
to=code_address,
data=b"",
gas_limit=1_000_000,
)
]

post = {code_address: Account(storage={slot: slot for slot in slots_under_test})}

state_test(
env=env,
pre=pre,
post=post,
txs=txs,
)


def test_tload_after_tstore_is_zero(state_test: StateTestFiller):
"""
Test that tload returns zero after tstore is called with zero.

Based on [ethereum/tests/.../03_tloadAfterStoreIs0Filler.yml](https://github.com/ethereum/tests/blob/9b00b68593f5869eb51a6659e1cc983e875e616b/src/EIPTestsFiller/StateTests/stEIP1153-transientStorage/03_tloadAfterStoreIs0Filler.yml)", # noqa: E501
"""
env = Environment()

slots_to_write = [1, 4, 2**128, 2**256 - 2]
slots_to_read = [slot - 1 for slot in slots_to_write] + [slot + 1 for slot in slots_to_write]
assert set.intersection(set(slots_to_write), set(slots_to_read)) == set()

code = b"".join([Op.TSTORE(slot, 1234) for slot in slots_to_write]) + b"".join(
[Op.SSTORE(slot, Op.TLOAD(slot)) for slot in slots_to_read]
)

pre = {
TestAddress: Account(balance=10_000_000),
code_address: Account(
code=code, storage={slot: 0xFFFF for slot in slots_to_write + slots_to_read}
),
}

txs = [
Transaction(
to=code_address,
data=b"",
gas_limit=1_000_000,
)
]

post = {
code_address: Account(
storage={slot: 0 for slot in slots_to_read} | {slot: 0xFFFF for slot in slots_to_write}
)
}

state_test(
env=env,
pre=pre,
post=post,
txs=txs,
)
Loading