Skip to content

Commit

Permalink
tests: add more transient storage/eip-1153 tests (#292)
Browse files Browse the repository at this point in the history
* tests: add tstorage gas measure tests

* tests: port simple tload follow sstore test from ethereum/tests

* tests: add call to invalid/revert/oog exec contexts

Also fix the arguments pushed for the delegatecall opcode; there was one too many.

* tests/cancun/eip1153: small fix to storage pre

* tests/cancun/eip1153: add self-destruct tests

* tox: add 'selfdestructing' to whitelist.txt

* style: fix incorrect comment

* tests: fix xfail for revert by ensuring memory is clean

* tests: add tstore in call then tload return in staticcall

---------

Co-authored-by: Mario Vega <[email protected]>
Co-authored-by: spencer-tb <[email protected]>
  • Loading branch information
3 people authored Sep 13, 2023
1 parent 0ef22d8 commit c95b81c
Show file tree
Hide file tree
Showing 5 changed files with 490 additions and 47 deletions.
123 changes: 120 additions & 3 deletions tests/cancun/eip1153_tstore/test_tstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
[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
from enum import unique

import pytest

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

from .spec import ref_spec_1153
from . import PytestParameterEnum
from .spec import Spec, ref_spec_1153

REFERENCE_SPEC_GIT_PATH = ref_spec_1153.git_path
REFERENCE_SPEC_VERSION = ref_spec_1153.version
Expand Down Expand Up @@ -96,6 +97,52 @@ def test_tload_after_tstore(state_test: StateTestFiller):
)


def test_tload_after_sstore(state_test: StateTestFiller):
"""
Loading after storing returns the stored value: TSTORE(x, y), TLOAD(x)
returns y.
Based on [ethereum/tests/.../18_tloadAfterStoreFiller.yml](https://github.com/ethereum/tests/blob/9b00b68593f5869eb51a6659e1cc983e875e616b/src/EIPTestsFiller/StateTests/stEIP1153-transientStorage/18_tloadAfterStoreFiller.yml)", # noqa: E501
"""
env = Environment()

slots_under_test = [1, 3, 2**128, 2**256 - 1]
code = b"".join(
[
Op.SSTORE(slot - 1, 0xFF) + Op.SSTORE(slot, Op.TLOAD(slot - 1))
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(
code=code,
storage={slot - 1: 0xFF for slot in slots_under_test}
| {slot: 0 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.
Expand Down Expand Up @@ -139,3 +186,73 @@ def test_tload_after_tstore_is_zero(state_test: StateTestFiller):
post=post,
txs=txs,
)


@unique
class GasMeasureTestCases(PytestParameterEnum):
"""
Test cases for gas measurement.
"""

TLOAD = {
"description": "Test that tload() of an empty slot consumes the expected gas.",
"bytecode": Op.TLOAD(10),
"overhead_cost": 3, # 1 x PUSH1
"extra_stack_items": 1,
"expected_gas": Spec.TLOAD_GAS_COST,
}
TSTORE_TLOAD = {
"description": "Test that tload() of a used slot consumes the expected gas.",
"bytecode": Op.TSTORE(10, 10) + Op.TLOAD(10),
"overhead_cost": 3 * 3, # 3 x PUSH1
"extra_stack_items": 1,
"expected_gas": Spec.TSTORE_GAS_COST + Spec.TLOAD_GAS_COST,
}
TSTORE_COLD = {
"description": "Test that tstore() of a previously unused slot consumes the expected gas.",
"bytecode": Op.TSTORE(10, 10),
"overhead_cost": 2 * 3, # 2 x PUSH1
"extra_stack_items": 0,
"expected_gas": Spec.TSTORE_GAS_COST,
}
TSTORE_WARM = {
"description": "Test that tstore() of a previously used slot consumes the expected gas.",
"bytecode": Op.TSTORE(10, 10) + Op.TSTORE(10, 11),
"overhead_cost": 4 * 3, # 4 x PUSH1
"extra_stack_items": 0,
"expected_gas": 2 * Spec.TSTORE_GAS_COST,
}


@GasMeasureTestCases.parametrize()
def test_gas_usage(
state_test: StateTestFiller,
bytecode: Code,
expected_gas: int,
overhead_cost: int,
extra_stack_items: int,
):
"""
Test that tstore and tload consume the expected gas.
"""
gas_measure_bytecode = CodeGasMeasure(
code=bytecode, overhead_cost=overhead_cost, extra_stack_items=extra_stack_items
)

env = Environment()
pre = {
TestAddress: Account(balance=10_000_000, nonce=0),
code_address: Account(code=gas_measure_bytecode),
}
txs = [
Transaction(
to=code_address,
data=b"",
gas_limit=1_000_000,
)
]
post = {
code_address: Account(code=gas_measure_bytecode, storage={0: expected_gas}),
TestAddress: Account(nonce=1),
}
state_test(env=env, pre=pre, txs=txs, post=post)
107 changes: 72 additions & 35 deletions tests/cancun/eip1153_tstore/test_tstorage_execution_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,20 @@ class DynamicCallContextTestCases(EnumMeta):

def __new__(cls, name, bases, classdict): # noqa: D102
for opcode in [Op.CALLCODE, Op.DELEGATECALL]:
if opcode == Op.DELEGATECALL:
contract_call = opcode(Op.GAS(), callee_address, 0, 0, 0, 0)
elif opcode == Op.CALLCODE:
contract_call = opcode(Op.GAS(), callee_address, 0, 0, 0, 0, 0)
else:
raise ValueError("Unexpected opcode.")
classdict[opcode._name_] = {
"description": (
"Caller and callee contracts share transient storage when callee is "
f"called via {opcode._name_}."
),
"caller_bytecode": (
Op.TSTORE(0, 420)
+ Op.SSTORE(0, opcode(Op.GAS(), callee_address, 0, 0, 0, 0, 0))
+ Op.SSTORE(0, contract_call)
+ Op.SSTORE(1, Op.TLOAD(0))
+ Op.SSTORE(4, Op.TLOAD(1))
),
Expand All @@ -56,50 +62,69 @@ def __new__(cls, name, bases, classdict): # noqa: D102
"expected_caller_storage": {0: 1, 1: 420, 2: 420, 3: 69, 4: 69},
"expected_callee_storage": {},
}

for opcode in [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]:
if opcode == Op.DELEGATECALL:
contract_call = opcode(Op.GAS(), callee_address, 0, 0, 0, 0)
elif opcode in [Op.CALL, Op.CALLCODE]:
contract_call = opcode(Op.GAS(), callee_address, 0, 0, 0, 0, 0)
else:
raise ValueError("Unexpected opcode.")
classdict[f"{opcode._name_}_WITH_REVERT"] = {
"description": (
"Caller and callee contracts share transient storage when callee is "
f"called via {opcode._name_} but transient storage usage is discarded "
"from sub-call upon REVERT."
f"Transient storage usage is discarded from sub-call with {opcode._name_} "
"upon REVERT."
),
"caller_bytecode": (
Op.TSTORE(0, 420)
+ Op.TSTORE(1, 420)
+ Op.SSTORE(0, opcode(Op.GAS(), callee_address, 0, 0, 0, 0, 0))
+ Op.SSTORE(0, contract_call)
+ Op.SSTORE(1, Op.TLOAD(0))
+ Op.SSTORE(2, Op.TLOAD(1))
),
"callee_bytecode": Op.TSTORE(1, 69) + Op.REVERT(0, 0),
"expected_caller_storage": {0: 0, 1: 420, 2: 420},
"expected_callee_storage": {},
}

if opcode == Op.DELEGATECALL:
contract_call = opcode(0xFF, callee_address, 0, 0, 0, 0)
elif opcode in [Op.CALL, Op.CALLCODE]:
contract_call = opcode(0xFF, callee_address, 0, 0, 0, 0, 0)
else:
raise ValueError("Unexpected opcode.")
classdict[f"{opcode._name_}_WITH_INVALID"] = {
"description": (
"Caller and callee contracts share transient storage when callee is "
f"called via {opcode._name_} but transient storage usage is discarded "
"from sub-call upon INVALID. Note: Gas passed to sub-call is capped."
f"Transient storage usage is discarded from sub-call with {opcode._name_} "
"upon REVERT. Note: Gas passed to sub-call is capped."
),
"caller_bytecode": (
Op.TSTORE(0, 420)
+ Op.TSTORE(1, 420)
+ Op.SSTORE(0, opcode(0xFF, callee_address, 0, 0, 0, 0, 0))
+ Op.SSTORE(0, contract_call)
+ Op.SSTORE(1, Op.TLOAD(0))
+ Op.SSTORE(2, Op.TLOAD(1))
),
"callee_bytecode": Op.TSTORE(1, 69) + Op.INVALID(),
"expected_caller_storage": {0: 0, 1: 420, 2: 420},
"expected_callee_storage": {},
}

if opcode == Op.DELEGATECALL:
contract_call = opcode(0xFFFF, callee_address, 0, 0, 0, 0)
elif opcode in [Op.CALL, Op.CALLCODE]:
contract_call = opcode(0xFFFF, callee_address, 0, 0, 0, 0, 0)
else:
raise ValueError("Unexpected opcode.")
classdict[f"{opcode._name_}_WITH_STACK_OVERFLOW"] = {
"description": (
"Caller and callee contracts share transient storage when callee is "
f"called via {opcode._name_} but transient storage usage is discarded "
"from sub-call upon stack overflow."
f"Transient storage usage is discarded from sub-call with {opcode._name_} "
"upon stack overflow."
),
"caller_bytecode": (
Op.TSTORE(0, 420)
+ Op.TSTORE(1, 420)
+ Op.SSTORE(0, opcode(0xFFFF, callee_address, 0, 0, 0, 0, 0))
+ Op.SSTORE(0, contract_call)
+ Op.SSTORE(1, Op.TLOAD(0))
+ Op.SSTORE(2, Op.TLOAD(1))
),
Expand All @@ -109,14 +134,13 @@ def __new__(cls, name, bases, classdict): # noqa: D102
}
classdict[f"{opcode._name_}_WITH_TSTORE_STACK_UNDERFLOW"] = {
"description": (
"Caller and callee contracts share transient storage when callee is "
f"called via {opcode._name_} but transient storage usage is discarded "
"from sub-call upon stack underflow because of TSTORE parameters (1)."
f"Transient storage usage is discarded from sub-call with {opcode._name_} "
"upon stack underflow because of TSTORE parameters (1)."
),
"caller_bytecode": (
Op.TSTORE(0, 420)
+ Op.TSTORE(1, 420)
+ Op.SSTORE(0, opcode(0xFFFF, callee_address, 0, 0, 0, 0, 0))
+ Op.SSTORE(0, contract_call)
+ Op.SSTORE(1, Op.TLOAD(0))
+ Op.SSTORE(2, Op.TLOAD(1))
),
Expand All @@ -126,14 +150,13 @@ def __new__(cls, name, bases, classdict): # noqa: D102
}
classdict[f"{opcode._name_}_WITH_TSTORE_STACK_UNDERFLOW_2"] = {
"description": (
"Caller and callee contracts share transient storage when callee is "
f"called via {opcode._name_} but transient storage usage is discarded "
"from sub-call upon stack underflow because of TSTORE parameters (0)."
f"Transient storage usage is discarded from sub-call with {opcode._name_} "
" upon stack underflow because of TSTORE parameters (0)."
),
"caller_bytecode": (
Op.TSTORE(0, 420)
+ Op.TSTORE(1, 420)
+ Op.SSTORE(0, opcode(0xFFFF, callee_address, 0, 0, 0, 0, 0))
+ Op.SSTORE(0, contract_call)
+ Op.SSTORE(1, Op.TLOAD(0))
+ Op.SSTORE(2, Op.TLOAD(1))
),
Expand All @@ -143,27 +166,36 @@ def __new__(cls, name, bases, classdict): # noqa: D102
}
classdict[f"{opcode._name_}_WITH_TLOAD_STACK_UNDERFLOW"] = {
"description": (
"Caller and callee contracts share transient storage when callee is "
f"called via {opcode._name_} but transient storage usage is discarded "
"from sub-call upon stack underflow because of TLOAD parameters (0)."
f"Transient storage usage is discarded from sub-call with {opcode._name_} "
"upon stack underflow because of TLOAD parameters (0)."
),
"caller_bytecode": (
Op.TSTORE(0, 420)
+ Op.TSTORE(1, 420)
+ Op.SSTORE(0, opcode(0xFFFF, callee_address, 0, 0, 0, 0, 0))
+ Op.SSTORE(0, contract_call)
+ Op.SSTORE(1, Op.TLOAD(0))
+ Op.SSTORE(2, Op.TLOAD(1))
),
"callee_bytecode": Op.TLOAD + Op.TSTORE(0, 1),
"expected_caller_storage": {0: 0, 1: 420, 2: 420},
"expected_callee_storage": {},
}

gas_limit = Spec.TSTORE_GAS_COST + (PUSH_OPCODE_COST * 2) - 1
if opcode == Op.DELEGATECALL:
contract_call = opcode(
opcode(gas_limit, callee_address, 0, 0, 0, 0),
)
elif opcode in [Op.CALL, Op.CALLCODE]:
contract_call = opcode(
opcode(gas_limit, callee_address, 0, 0, 0, 0, 0),
)
else:
raise ValueError("Unexpected opcode.")
classdict[f"{opcode._name_}_WITH_OUT_OF_GAS"] = {
"description": (
"Caller and callee contracts share transient storage when callee is "
f"called via {opcode._name_} but transient storage usage is discarded "
"from sub-call upon out of gas during TSTORE. Note: Gas passed to sub-call is "
"capped."
f"Transient storage usage is discarded from sub-call with {opcode._name_} "
"upon out of gas during TSTORE. Note: Gas passed to sub-call is capped."
),
"caller_bytecode": (
Op.TSTORE(0, 420)
Expand All @@ -187,17 +219,22 @@ def __new__(cls, name, bases, classdict): # noqa: D102
"expected_caller_storage": {0: 0, 1: 420, 2: 420},
"expected_callee_storage": {},
}

if opcode == Op.DELEGATECALL:
contract_call = opcode(0xFF, callee_address, 0, 0, 0, 0)
elif opcode in [Op.CALL, Op.CALLCODE]:
contract_call = opcode(0xFF, callee_address, 0, 0, 0, 0, 0)
else:
raise ValueError("Unexpected opcode.")
classdict[f"{opcode._name_}_WITH_OUT_OF_GAS_2"] = {
"description": (
"Caller and callee contracts share transient storage when callee is "
f"called via {opcode._name_} but transient storage usage is discarded "
"from sub-call upon out of gas after TSTORE. Note: Gas passed to sub-call is "
"capped."
f"Transient storage usage is discarded from sub-call with {opcode._name_} "
"upon out of gas after TSTORE. Note: Gas passed to sub-call is capped."
),
"caller_bytecode": (
Op.TSTORE(0, 420)
+ Op.TSTORE(1, 420)
+ Op.SSTORE(0, opcode(0xFF, callee_address, 0, 0, 0, 0, 0))
+ Op.SSTORE(0, contract_call)
+ Op.SSTORE(1, Op.TLOAD(0))
+ Op.SSTORE(2, Op.TLOAD(1))
),
Expand Down Expand Up @@ -264,7 +301,7 @@ class CallContextTestCases(PytestParameterEnum, metaclass=DynamicCallContextTest
+ Op.SSTORE(0, Op.STATICCALL(Op.GAS(), callee_address, 0, 0, 0, 0))
+ Op.SSTORE(1, Op.TLOAD(0))
),
"callee_bytecode": Op.TLOAD(0), # calling tload does fail the call
"callee_bytecode": Op.TLOAD(0), # calling tload does not cause the call to fail
"expected_caller_storage": {0: 1, 1: 420},
"expected_callee_storage": {},
}
Expand Down
Loading

0 comments on commit c95b81c

Please sign in to comment.