From 92284a5783a8f01ef7ff8822e53b65bb2d64c33f Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Mon, 28 Oct 2024 12:24:12 +0100 Subject: [PATCH] test scenarios --- .github/workflows/coverage.yaml | 6 +- converted-ethereum-tests.txt | 116 +++++ .../composite_types.py | 45 +- tests/frontier/scenarios/__init__.py | 3 + tests/frontier/scenarios/common.py | 172 ++++++++ tests/frontier/scenarios/programs/__init__.py | 3 + .../programs/all_frontier_opcodes.py | 399 ++++++++++++++++++ .../scenarios/programs/context_calls.py | 244 +++++++++++ .../frontier/scenarios/scenarios/__init__.py | 3 + .../scenarios/scenarios/call_combinations.py | 300 +++++++++++++ .../scenarios/create_combinations.py | 177 ++++++++ .../scenarios/revert_combinations.py | 46 ++ tests/frontier/scenarios/test_scenarios.py | 289 +++++++++++++ 13 files changed, 1789 insertions(+), 14 deletions(-) create mode 100644 tests/frontier/scenarios/__init__.py create mode 100644 tests/frontier/scenarios/common.py create mode 100644 tests/frontier/scenarios/programs/__init__.py create mode 100644 tests/frontier/scenarios/programs/all_frontier_opcodes.py create mode 100644 tests/frontier/scenarios/programs/context_calls.py create mode 100644 tests/frontier/scenarios/scenarios/__init__.py create mode 100644 tests/frontier/scenarios/scenarios/call_combinations.py create mode 100644 tests/frontier/scenarios/scenarios/create_combinations.py create mode 100644 tests/frontier/scenarios/scenarios/revert_combinations.py create mode 100644 tests/frontier/scenarios/test_scenarios.py diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index e1874f6f56..93d0f0044d 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -179,8 +179,7 @@ jobs: mkdir -p fixtures/eof_tests echo "uv run fill $files --until=Cancun --evm-bin evmone-t8n >> filloutput.log 2>&1" - uv run fill $files --until=Cancun --evm-bin evmone-t8n >> filloutput.log 2>&1 - cat filloutput.log + uv run fill $files --until=Cancun --evm-bin evmone-t8n > >(tee -a filloutput.log) 2> >(tee -a filloutput.log >&2) if grep -q "FAILURES" filloutput.log; then echo "Error: failed to generate .py tests." @@ -235,8 +234,7 @@ jobs: if [ -n "$files_fixed" ]; then echo "uv run fill $files_fixed --until=Cancun --evm-bin evmone-t8n >> filloutput.log 2>&1" - uv run fill $files_fixed --until=Cancun --evm-bin evmone-t8n >> filloutput.log 2>&1 - cat filloutput.log + uv run fill $files_fixed --until=Cancun --evm-bin evmone-t8n > >(tee -a filloutput.log) 2> >(tee -a filloutput.log >&2) if grep -q "FAILURES" filloutput.log; then echo "Error: failed to generate .py tests from before the PR." diff --git a/converted-ethereum-tests.txt b/converted-ethereum-tests.txt index b42c9749c1..143120868a 100644 --- a/converted-ethereum-tests.txt +++ b/converted-ethereum-tests.txt @@ -1,3 +1,119 @@ +GeneralStateTests/stBadOpcode/opc0CDiffPlaces.json +GeneralStateTests/stBadOpcode/opc0DDiffPlaces.json +GeneralStateTests/stBadOpcode/opc0EDiffPlaces.json +GeneralStateTests/stBadOpcode/opc0FDiffPlaces.json +GeneralStateTests/stBadOpcode/opc1EDiffPlaces.json +GeneralStateTests/stBadOpcode/opc1FDiffPlaces.json +GeneralStateTests/stBadOpcode/opc2ADiffPlaces.json +GeneralStateTests/stBadOpcode/opc2BDiffPlaces.json +GeneralStateTests/stBadOpcode/opc2CDiffPlaces.json +GeneralStateTests/stBadOpcode/opc2DDiffPlaces.json +GeneralStateTests/stBadOpcode/opc2EDiffPlaces.json +GeneralStateTests/stBadOpcode/opc2FDiffPlaces.json +GeneralStateTests/stBadOpcode/opc4ADiffPlaces.json +GeneralStateTests/stBadOpcode/opc4BDiffPlaces.json +GeneralStateTests/stBadOpcode/opc4CDiffPlaces.json +GeneralStateTests/stBadOpcode/opc4DDiffPlaces.json +GeneralStateTests/stBadOpcode/opc4EDiffPlaces.json +GeneralStateTests/stBadOpcode/opc4FDiffPlaces.json +GeneralStateTests/stBadOpcode/opc5CDiffPlaces.json +GeneralStateTests/stBadOpcode/opc5DDiffPlaces.json +GeneralStateTests/stBadOpcode/opc5EDiffPlaces.json +GeneralStateTests/stBadOpcode/opc5FDiffPlaces.json +GeneralStateTests/stBadOpcode/opc21DiffPlaces.json +GeneralStateTests/stBadOpcode/opc22DiffPlaces.json +GeneralStateTests/stBadOpcode/opc23DiffPlaces.json +GeneralStateTests/stBadOpcode/opc24DiffPlaces.json +GeneralStateTests/stBadOpcode/opc25DiffPlaces.json +GeneralStateTests/stBadOpcode/opc26DiffPlaces.json +GeneralStateTests/stBadOpcode/opc27DiffPlaces.json +GeneralStateTests/stBadOpcode/opc28DiffPlaces.json +GeneralStateTests/stBadOpcode/opc29DiffPlaces.json +GeneralStateTests/stBadOpcode/opc49DiffPlaces.json +GeneralStateTests/stBadOpcode/opcA5DiffPlaces.json +GeneralStateTests/stBadOpcode/opcA6DiffPlaces.json +GeneralStateTests/stBadOpcode/opcA7DiffPlaces.json +GeneralStateTests/stBadOpcode/opcA8DiffPlaces.json +GeneralStateTests/stBadOpcode/opcA9DiffPlaces.json +GeneralStateTests/stBadOpcode/opcAADiffPlaces.json +GeneralStateTests/stBadOpcode/opcABDiffPlaces.json +GeneralStateTests/stBadOpcode/opcACDiffPlaces.json +GeneralStateTests/stBadOpcode/opcADDiffPlaces.json +GeneralStateTests/stBadOpcode/opcAEDiffPlaces.json +GeneralStateTests/stBadOpcode/opcAFDiffPlaces.json +GeneralStateTests/stBadOpcode/opcB0DiffPlaces.json +GeneralStateTests/stBadOpcode/opcB1DiffPlaces.json +GeneralStateTests/stBadOpcode/opcB2DiffPlaces.json +GeneralStateTests/stBadOpcode/opcB3DiffPlaces.json +GeneralStateTests/stBadOpcode/opcB4DiffPlaces.json +GeneralStateTests/stBadOpcode/opcB5DiffPlaces.json +GeneralStateTests/stBadOpcode/opcB6DiffPlaces.json +GeneralStateTests/stBadOpcode/opcB7DiffPlaces.json +GeneralStateTests/stBadOpcode/opcB8DiffPlaces.json +GeneralStateTests/stBadOpcode/opcB9DiffPlaces.json +GeneralStateTests/stBadOpcode/opcBADiffPlaces.json +GeneralStateTests/stBadOpcode/opcBBDiffPlaces.json +GeneralStateTests/stBadOpcode/opcBCDiffPlaces.json +GeneralStateTests/stBadOpcode/opcBDDiffPlaces.json +GeneralStateTests/stBadOpcode/opcBEDiffPlaces.json +GeneralStateTests/stBadOpcode/opcBFDiffPlaces.json +GeneralStateTests/stBadOpcode/opcC0DiffPlaces.json +GeneralStateTests/stBadOpcode/opcC1DiffPlaces.json +GeneralStateTests/stBadOpcode/opcC2DiffPlaces.json +GeneralStateTests/stBadOpcode/opcC3DiffPlaces.json +GeneralStateTests/stBadOpcode/opcC4DiffPlaces.json +GeneralStateTests/stBadOpcode/opcC5DiffPlaces.json +GeneralStateTests/stBadOpcode/opcC6DiffPlaces.json +GeneralStateTests/stBadOpcode/opcC7DiffPlaces.json +GeneralStateTests/stBadOpcode/opcC8DiffPlaces.json +GeneralStateTests/stBadOpcode/opcC9DiffPlaces.json +GeneralStateTests/stBadOpcode/opcCADiffPlaces.json +GeneralStateTests/stBadOpcode/opcCBDiffPlaces.json +GeneralStateTests/stBadOpcode/opcCCDiffPlaces.json +GeneralStateTests/stBadOpcode/opcCDDiffPlaces.json +GeneralStateTests/stBadOpcode/opcCEDiffPlaces.json +GeneralStateTests/stBadOpcode/opcCFDiffPlaces.json +GeneralStateTests/stBadOpcode/opcD0DiffPlaces.json +GeneralStateTests/stBadOpcode/opcD1DiffPlaces.json +GeneralStateTests/stBadOpcode/opcD2DiffPlaces.json +GeneralStateTests/stBadOpcode/opcD3DiffPlaces.json +GeneralStateTests/stBadOpcode/opcD4DiffPlaces.json +GeneralStateTests/stBadOpcode/opcD5DiffPlaces.json +GeneralStateTests/stBadOpcode/opcD6DiffPlaces.json +GeneralStateTests/stBadOpcode/opcD7DiffPlaces.json +GeneralStateTests/stBadOpcode/opcD8DiffPlaces.json +GeneralStateTests/stBadOpcode/opcD9DiffPlaces.json +GeneralStateTests/stBadOpcode/opcDADiffPlaces.json +GeneralStateTests/stBadOpcode/opcDBDiffPlaces.json +GeneralStateTests/stBadOpcode/opcDCDiffPlaces.json +GeneralStateTests/stBadOpcode/opcDDDiffPlaces.json +GeneralStateTests/stBadOpcode/opcDEDiffPlaces.json +GeneralStateTests/stBadOpcode/opcDFDiffPlaces.json +GeneralStateTests/stBadOpcode/opcE0DiffPlaces.json +GeneralStateTests/stBadOpcode/opcE1DiffPlaces.json +GeneralStateTests/stBadOpcode/opcE2DiffPlaces.json +GeneralStateTests/stBadOpcode/opcE3DiffPlaces.json +GeneralStateTests/stBadOpcode/opcE4DiffPlaces.json +GeneralStateTests/stBadOpcode/opcE5DiffPlaces.json +GeneralStateTests/stBadOpcode/opcE6DiffPlaces.json +GeneralStateTests/stBadOpcode/opcE7DiffPlaces.json +GeneralStateTests/stBadOpcode/opcE8DiffPlaces.json +GeneralStateTests/stBadOpcode/opcE9DiffPlaces.json +GeneralStateTests/stBadOpcode/opcEADiffPlaces.json +GeneralStateTests/stBadOpcode/opcEBDiffPlaces.json +GeneralStateTests/stBadOpcode/opcECDiffPlaces.json +GeneralStateTests/stBadOpcode/opcEDDiffPlaces.json +GeneralStateTests/stBadOpcode/opcEEDiffPlaces.json +GeneralStateTests/stBadOpcode/opcEFDiffPlaces.json +GeneralStateTests/stBadOpcode/opcF6DiffPlaces.json +GeneralStateTests/stBadOpcode/opcF7DiffPlaces.json +GeneralStateTests/stBadOpcode/opcF8DiffPlaces.json +GeneralStateTests/stBadOpcode/opcF9DiffPlaces.json +GeneralStateTests/stBadOpcode/opcFBDiffPlaces.json +GeneralStateTests/stBadOpcode/opcFCDiffPlaces.json +GeneralStateTests/stBadOpcode/opcFEDiffPlaces.json + + ([#748](https://github.com/ethereum/execution-spec-tests/pull/748)) GeneralStateTests/stBadOpcode/badOpcodes.json GeneralStateTests/stBugs/evmBytecode.json diff --git a/src/ethereum_test_base_types/composite_types.py b/src/ethereum_test_base_types/composite_types.py index 7627c5b107..0b9a31f847 100644 --- a/src/ethereum_test_base_types/composite_types.py +++ b/src/ethereum_test_base_types/composite_types.py @@ -1,6 +1,7 @@ """ Base composite types for Ethereum test cases. """ + from dataclasses import dataclass from typing import Any, ClassVar, Dict, SupportsBytes, Type, TypeAlias @@ -24,6 +25,7 @@ class Storage(RootModel[Dict[StorageKeyValueType, StorageKeyValueType]]): root: Dict[StorageKeyValueType, StorageKeyValueType] = Field(default_factory=dict) _current_slot: int = PrivateAttr(0) + _hint_map: Dict[StorageKeyValueType, str] = {} StorageDictType: ClassVar[TypeAlias] = Dict[ str | int | bytes | SupportsBytes, str | int | bytes | SupportsBytes @@ -92,13 +94,15 @@ class KeyValueMismatch(Exception): key: int want: int got: int + hint: str - def __init__(self, address: Address, key: int, want: int, got: int, *args): + def __init__(self, address: Address, key: int, want: int, got: int, hint: str = "", *args): super().__init__(args) self.address = address self.key = key self.want = want self.got = got + self.hint = hint def __str__(self): """Print exception string""" @@ -107,7 +111,7 @@ def __str__(self): label_str = f" ({self.address.label})" return ( f"incorrect value in address {self.address}{label_str} for " - + f"key {Hash(self.key)}:" + + f"key {Hash(self.key)} ({self.hint}):" + f" want {HexNumber(self.want)} (dec:{int(self.want)})," + f" got {HexNumber(self.got)} (dec:{int(self.got)})" ) @@ -128,9 +132,9 @@ def __setitem__( value: StorageKeyValueTypeConvertible | StorageKeyValueType, ): # noqa: SC200 """Sets an item in the storage""" - self.root[ - StorageKeyValueTypeAdapter.validate_python(key) - ] = StorageKeyValueTypeAdapter.validate_python(value) + self.root[StorageKeyValueTypeAdapter.validate_python(key)] = ( + StorageKeyValueTypeAdapter.validate_python(value) + ) def __delitem__(self, key: StorageKeyValueTypeConvertible | StorageKeyValueType): """Deletes an item from the storage""" @@ -182,7 +186,7 @@ def items(self): return self.root.items() def store_next( - self, value: StorageKeyValueTypeConvertible | StorageKeyValueType | bool + self, value: StorageKeyValueTypeConvertible | StorageKeyValueType | bool, hint: str = "" ) -> StorageKeyValueType: """ Stores a value in the storage and returns the key where the value is stored. @@ -192,6 +196,7 @@ def store_next( """ slot = StorageKeyValueTypeAdapter.validate_python(self._current_slot) self._current_slot += 1 + self._hint_map[slot] = hint self[slot] = StorageKeyValueTypeAdapter.validate_python(value) return slot @@ -230,7 +235,11 @@ def must_contain(self, address: Address, other: "Storage"): raise Storage.MissingKey(key=key) elif self[key] != other[key]: raise Storage.KeyValueMismatch( - address=address, key=key, want=self[key], got=other[key] + address=address, + key=key, + want=self[key], + got=other[key], + hint=self._hint_map.get(key, ""), ) def must_be_equal(self, address: Address, other: "Storage | None"): @@ -243,17 +252,33 @@ def must_be_equal(self, address: Address, other: "Storage | None"): for key in self.keys() & other.keys(): if self[key] != other[key]: raise Storage.KeyValueMismatch( - address=address, key=key, want=self[key], got=other[key] + address=address, + key=key, + want=self[key], + got=other[key], + hint=self._hint_map.get(key, ""), ) # Test keys contained in either one of the storage objects for key in self.keys() ^ other.keys(): if key in self: if self[key] != 0: - raise Storage.KeyValueMismatch(address=address, key=key, want=self[key], got=0) + raise Storage.KeyValueMismatch( + address=address, + key=key, + want=self[key], + got=0, + hint=self._hint_map.get(key, ""), + ) elif other[key] != 0: - raise Storage.KeyValueMismatch(address=address, key=key, want=0, got=other[key]) + raise Storage.KeyValueMismatch( + address=address, + key=key, + want=0, + got=other[key], + hint=self._hint_map.get(key, ""), + ) def canary(self) -> "Storage": """ diff --git a/tests/frontier/scenarios/__init__.py b/tests/frontier/scenarios/__init__.py new file mode 100644 index 0000000000..20f2893c0e --- /dev/null +++ b/tests/frontier/scenarios/__init__.py @@ -0,0 +1,3 @@ +""" +Scenarios common import +""" diff --git a/tests/frontier/scenarios/common.py b/tests/frontier/scenarios/common.py new file mode 100644 index 0000000000..fa923fa39f --- /dev/null +++ b/tests/frontier/scenarios/common.py @@ -0,0 +1,172 @@ +""" +Define Scenario class for test_scenarios test +""" + +from dataclasses import dataclass +from enum import Enum + +from ethereum_test_forks import Fork, Frontier +from ethereum_test_tools import Address, Alloc, Bytecode, Hash + + +class ScenarioExpectOpcode(Enum): + """Opcodes that are replaced to real values computed by the scenario""" + + TX_ORIGIN = 1 + CODE_ADDRESS = 2 + CODE_CALLER = 3 + SELFBALANCE = 4 + BALANCE = 5 + CALL_VALUE = 6 + CALL_DATALOAD_0 = 7 + CALL_DATASIZE = 8 + GASPRICE = 9 + BLOCKHASH_0 = 10 + COINBASE = 11 + TIMESTAMP = 12 + NUMBER = 13 + DIFFICULTY_RANDAO = 14 + GASLIMIT = 15 + BASEFEE = 16 + BLOBHASH_0 = 17 + BLOBBASEFEE = 18 + + +@dataclass +class ProgramResult: + """ + Describe expected result of a program + + Attributes: + result (int | ScenarioExpectOpcode): The result of the program + from_fork (Fork): The result is only valid from this fork (default: Frontier) + """ + + result: int | ScenarioExpectOpcode + + """The result is only valid from this fork""" + from_fork: Fork = Frontier + + +@dataclass +class ScenarioEnvironment: + """ + Scenario evm environment + Each scenario must define an environment on which program is executed + This is so post state verification could check results of evm opcodes + """ + + code_address: Address # Op.ADDRESS, address scope for program + code_caller: Address # Op.CALLER, caller of the program + selfbalance: int # Op.SELFBALANCE, balance of the environment of the program + ext_balance: int # Op.BALANCE(external) of fixed address deployed in state + call_value: int # Op.CALLVALUE of call that is done to the program + call_dataload_0: int # Op.CALLDATALOAD(0) expected result + call_datasize: int # Op.CALLDATASIZE expected result + + +@dataclass +class ExecutionEnvironment: + """ + Scenario execution environment which is determined by test + """ + + fork: Fork + gasprice: int + origin: Address + coinbase: Address + blockhash_0: Hash + timestamp: int + number: int + difficulty_randao: int + gaslimit: int + basefee: int + blobhash_0: Hash + blob_basefee: int + + +@dataclass +class ScenarioGeneratorInput: + """ + Parameters for the scenario generator function + + Attributes: + fork (Fork): Fork for which we ask to generate scenarios + pre (Alloc): Access to the state to be able to deploy contracts into pre + operation (Bytecode): Evm bytecode program that will be tested + external_address (Address): Static external address for ext opcodes + """ + + fork: Fork + pre: Alloc + operation_code: Bytecode + external_address: Address + external_balance: int + + +@dataclass +class Scenario: + """ + Describe test scenario that will be run in test for each program + + Attributes: + name (str): Scenario name for the test vector + code (Address): Address that is an entry point for scenario code + env (ScenarioEnvironment): Evm values for ScenarioExpectAddress map + reverting (bool): If scenario reverts program execution, making result 0 (default: False) + """ + + name: str + code: Address + env: ScenarioEnvironment + reverting: bool = False + + +def translate_result( + res: ProgramResult, env: ScenarioEnvironment, exec_env: ExecutionEnvironment +) -> int: + """ + Translate expected program result code into concrete value, + given the scenario evm environment and test execution environment + """ + if exec_env.fork < res.from_fork: + return 0 + if isinstance(res.result, ScenarioExpectOpcode): + if res.result == ScenarioExpectOpcode.TX_ORIGIN: + return int(exec_env.origin.hex(), 16) + if res.result == ScenarioExpectOpcode.CODE_ADDRESS: + return int(env.code_address.hex(), 16) + if res.result == ScenarioExpectOpcode.CODE_CALLER: + return int(env.code_caller.hex(), 16) + if res.result == ScenarioExpectOpcode.BALANCE: + return int(env.ext_balance) + if res.result == ScenarioExpectOpcode.CALL_VALUE: + return int(env.call_value) + if res.result == ScenarioExpectOpcode.CALL_DATALOAD_0: + return env.call_dataload_0 + if res.result == ScenarioExpectOpcode.CALL_DATASIZE: + return env.call_datasize + if res.result == ScenarioExpectOpcode.GASPRICE: + return exec_env.gasprice + if res.result == ScenarioExpectOpcode.BLOCKHASH_0: + return int(exec_env.blockhash_0.hex(), 16) + if res.result == ScenarioExpectOpcode.COINBASE: + return int(exec_env.coinbase.hex(), 16) + if res.result == ScenarioExpectOpcode.TIMESTAMP: + return exec_env.timestamp + if res.result == ScenarioExpectOpcode.NUMBER: + return exec_env.number + if res.result == ScenarioExpectOpcode.DIFFICULTY_RANDAO: + return exec_env.difficulty_randao + if res.result == ScenarioExpectOpcode.GASLIMIT: + return exec_env.gaslimit + if res.result == ScenarioExpectOpcode.SELFBALANCE: + return int(env.selfbalance) + if res.result == ScenarioExpectOpcode.BASEFEE: + return exec_env.basefee + if res.result == ScenarioExpectOpcode.BLOBHASH_0: + return int(exec_env.blobhash_0.hex(), 16) + if res.result == ScenarioExpectOpcode.BLOBBASEFEE: + return exec_env.blob_basefee + + return res.result diff --git a/tests/frontier/scenarios/programs/__init__.py b/tests/frontier/scenarios/programs/__init__.py new file mode 100644 index 0000000000..20f2893c0e --- /dev/null +++ b/tests/frontier/scenarios/programs/__init__.py @@ -0,0 +1,3 @@ +""" +Scenarios common import +""" diff --git a/tests/frontier/scenarios/programs/all_frontier_opcodes.py b/tests/frontier/scenarios/programs/all_frontier_opcodes.py new file mode 100644 index 0000000000..b3a80d1a4f --- /dev/null +++ b/tests/frontier/scenarios/programs/all_frontier_opcodes.py @@ -0,0 +1,399 @@ +""" +Define a program for scenario test that executes all frontier opcodes and entangles it's result +""" + +import pytest + +from ethereum_test_tools import Bytecode, Conditional +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from ..common import ProgramResult + +# Opcodes that are not in Frontier +# 1b - SHL +# 1c - SHR +# 1d - SAR + + +def make_all_opcode_program() -> Bytecode: + """Make a program that call each Frontier opcode and verifies it's result""" + code: Bytecode = ( + # Test opcode 01 - ADD + Conditional( + condition=Op.EQ(Op.ADD(1, 1), 2), + if_true=Op.MSTORE(0, 2), + if_false=Op.MSTORE(0, 0) + Op.RETURN(0, 32), + ) + # Test opcode 02 - MUL + + Conditional( + condition=Op.EQ( + Op.MUL(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 2) + Op.RETURN(0, 32), + ) + # Test 03 - SUB + + Conditional( + condition=Op.EQ( + Op.SUB(0, 1), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 3) + Op.RETURN(0, 32), + ) + # Test 04 - DIV + + Conditional( + condition=Op.AND(Op.EQ(Op.DIV(1, 2), 0), Op.EQ(Op.DIV(10, 2), 5)), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 4) + Op.RETURN(0, 32), + ) + # Test 05 - SDIV + + Conditional( + condition=Op.EQ( + Op.SDIV( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + ), + 2, + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 5) + Op.RETURN(0, 32), + ) + # Test 06 - MOD + + Conditional( + condition=Op.AND( + Op.EQ(Op.MOD(10, 3), 1), + Op.EQ( + Op.MOD( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD, + ), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 6) + Op.RETURN(0, 32), + ) + # Test 07 - SMOD + + Conditional( + condition=Op.AND( + Op.EQ(Op.SMOD(10, 3), 1), + Op.EQ( + Op.SMOD( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD, + ), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 7) + Op.RETURN(0, 32), + ) + # Test 08 - ADDMOD + + Conditional( + condition=Op.AND( + Op.EQ(Op.ADDMOD(10, 10, 8), 4), + Op.EQ( + Op.ADDMOD( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 2, + 2, + ), + 1, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 8) + Op.RETURN(0, 32), + ) + # Test 09 - MULMOD + + Conditional( + condition=Op.AND( + Op.EQ(Op.MULMOD(10, 10, 8), 4), + Op.EQ( + Op.MULMOD( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 12, + ), + 9, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 9) + Op.RETURN(0, 32), + ) + # Test 0A - EXP + + Conditional( + condition=Op.AND( + Op.EQ(Op.EXP(10, 2), 100), + Op.EQ( + Op.EXP(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD, 2), + 9, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 10) + Op.RETURN(0, 32), + ) + # Test 0B - SIGNEXTEND + + Conditional( + condition=Op.AND( + Op.EQ( + Op.SIGNEXTEND(0, 0xFF), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + ), + Op.EQ( + Op.SIGNEXTEND(0, 0x7F), + 0x7F, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 11) + Op.RETURN(0, 32), + ) + # Test 10 - LT + + Conditional( + condition=Op.AND( + Op.EQ( + Op.LT(9, 10), + 1, + ), + Op.EQ( + Op.LT(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0), + 0, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x10) + Op.RETURN(0, 32), + ) + # Test 11 - GT + + Conditional( + condition=Op.AND( + Op.EQ( + Op.GT(9, 10), + 0, + ), + Op.EQ( + Op.GT(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0), + 1, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x11) + Op.RETURN(0, 32), + ) + # Test 12 - SLT + + Conditional( + condition=Op.AND( + Op.EQ( + Op.SLT(9, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF), + 0, + ), + Op.EQ( + Op.SLT(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0), + 1, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x12) + Op.RETURN(0, 32), + ) + # Test 13 - SGT + + Conditional( + condition=Op.AND( + Op.EQ( + Op.SGT(10, 10), + 0, + ), + Op.EQ( + Op.SGT(0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF), + 1, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x13) + Op.RETURN(0, 32), + ) + # Test 14 - EQ Skip + # Test 15 - ISZero + + Conditional( + condition=Op.AND( + Op.EQ(Op.ISZERO(10), 0), + Op.EQ(Op.ISZERO(0), 1), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x15) + Op.RETURN(0, 32), + ) + # Test 16 - AND Skip + # Test 17 - OR + + Conditional( + condition=Op.AND( + Op.EQ(Op.OR(0xF0, 0xF), 0xFF), + Op.EQ(Op.OR(0xFF, 0xFF), 0xFF), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x17) + Op.RETURN(0, 32), + ) + # Test 18 - XOR + + Conditional( + condition=Op.AND( + Op.EQ(Op.XOR(0xF0, 0xF), 0xFF), + Op.EQ(Op.XOR(0xFF, 0xFF), 0), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x18) + Op.RETURN(0, 32), + ) + # Test 19 - NOT + + Conditional( + condition=Op.AND( + Op.EQ( + Op.NOT(0), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ), + Op.EQ( + Op.NOT(0xFFFFFFFFFFFF), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000, + ), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x19) + Op.RETURN(0, 32), + ) + # Test 1A - BYTE + + Conditional( + condition=Op.AND( + Op.EQ(Op.BYTE(31, 0xFF), 0xFF), + Op.EQ(Op.BYTE(30, 0xFF00), 0xFF), + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x1A) + Op.RETURN(0, 32), + ) + # Test 20 - SHA3 + + Op.MSTORE(0, 0xFFFFFFFF) + + Conditional( + condition=Op.EQ( + Op.SHA3(28, 4), + 0x29045A592007D0C246EF02C2223570DA9522D0CF0F73282C79A1BC8F0BB2C238, + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x20) + Op.RETURN(0, 32), + ) + # 50 POP + # 51 MLOAD + # 52 MSTORE + # 53 MSTORE8 + + Op.MSTORE(0, 0) + + Op.MSTORE8(0, 0xFFFF) + + Conditional( + condition=Op.EQ( + Op.MLOAD(0), + 0xFF00000000000000000000000000000000000000000000000000000000000000, + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x53) + Op.RETURN(0, 32), + ) + # 54 SLOAD + + Conditional( + condition=Op.EQ(Op.SLOAD(0), 0), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x54) + Op.RETURN(0, 32), + ) + # 55 SSTORE # can't use because of static contexts + # 56 JUMP + # 57 JUMPI + # 58 PC + + Conditional( + condition=Op.EQ(Op.PC, 1660), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x58) + Op.RETURN(0, 32), + ) + # 59 MSIZE + + Op.MSTORE(64, 123) + + Conditional( + condition=Op.EQ(Op.MSIZE, 96), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 0x59) + Op.RETURN(0, 32), + ) + # 5A GAS + # 5B JUMPDEST + # 5C TLOAD + # 5D TSTORE # can't use because of static contexts + # 5E MCOPY + # 5F PUSH0 + # 60 - 7F PUSH X + + Op.PUSH1(0xFF) + + Op.PUSH2(0xFFFF) + + Op.ADD + + Op.PUSH3(0xFFFFFF) + + Op.ADD + + Op.PUSH4(0xFFFFFFFF) + + Op.ADD + + Op.PUSH5(0xFFFFFFFFFF) + + Op.ADD + + Op.PUSH6(0xFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH7(0xFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH8(0xFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH9(0xFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH10(0xFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH11(0xFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH12(0xFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH13(0xFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH14(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH15(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH16(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH17(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH18(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH19(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH20(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH21(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH22(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH23(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH24(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH25(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH26(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH27(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH28(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH29(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH30(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH31(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH32(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + + Op.ADD + + Op.PUSH1(0) + + Op.MSTORE + + Conditional( + condition=Op.EQ( + Op.MLOAD(0), 0x1010101010101010101010101010101010101010101010101010101010100E0 + ), + if_true=Op.JUMPDEST, + if_false=Op.MSTORE(0, 60) + Op.RETURN(0, 32), + ) + + Op.MSTORE(0, 1) + + Op.RETURN(0, 32) + ) + return code + + +program_all_frontier_opcodes = pytest.param( + make_all_opcode_program(), + ProgramResult(result=1), + id="program_ALL_FRONTIER_OPCODES", +) diff --git a/tests/frontier/scenarios/programs/context_calls.py b/tests/frontier/scenarios/programs/context_calls.py new file mode 100644 index 0000000000..d211ff50e8 --- /dev/null +++ b/tests/frontier/scenarios/programs/context_calls.py @@ -0,0 +1,244 @@ +""" +Define programs that will run all context opcodes for test scenarios +""" + +import pytest + +from ethereum_test_forks import Byzantium, Cancun, Constantinople, Istanbul, London, Shanghai +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from ..common import ProgramResult, ScenarioExpectOpcode + +# Check that ADDRESS is really the code execution address in all scenarios +program_address = pytest.param( + Op.MSTORE(0, Op.ADDRESS) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.CODE_ADDRESS), + id="program_ADDRESS", +) + +# Check the BALANCE in all execution contexts +program_balance = pytest.param( + Op.MSTORE(0, Op.BALANCE(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.BALANCE), + id="program_BALANCE", +) + +# Check that ORIGIN stays the same in all contexts +program_origin = pytest.param( + Op.MSTORE(0, Op.ORIGIN) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.TX_ORIGIN), + id="program_ORIGIN", +) + + +# Check the CALLER in all execution contexts +program_caller = pytest.param( + Op.MSTORE(0, Op.CALLER) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.CODE_CALLER), + id="program_CALLER", +) + +# Check the CALLVALUE in all execution contexts +program_callvalue = pytest.param( + Op.MSTORE(0, Op.CALLVALUE) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.CALL_VALUE), + id="program_CALLVALUE", +) + +# Check the CALLDATALOAD in all execution contexts +program_calldataload = pytest.param( + Op.MSTORE(0, Op.CALLDATALOAD(0)) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.CALL_DATALOAD_0), + id="program_CALLDATALOAD", +) + +# Check the CALLDATASIZE in all execution contexts +program_calldatasize = pytest.param( + Op.MSTORE(0, Op.CALLDATASIZE) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.CALL_DATASIZE), + id="program_CALLDATASIZE", +) + +# Check the CALLDATACOPY in all execution contexts +program_calldatacopy = pytest.param( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.CALL_DATALOAD_0), + id="program_CALLDATACOPY", +) + +# Check that codecopy and codesize stays the same in all contexts +program_codecopy_codesize = pytest.param( + Op.MSTORE(0, Op.CODESIZE) + Op.CODECOPY(0, 0, 30) + Op.RETURN(0, 32), + ProgramResult(result=0x38600052601E600060003960206000F300000000000000000000000000000010), + id="program_CODECOPY_CODESIZE", +) + +# Check that gasprice stays the same in all contexts +program_gasprice = pytest.param( + Op.MSTORE(0, Op.GASPRICE) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.GASPRICE), + id="program_GASPRICE", +) + +# Check that extcodesize and extcodecopy of pre deployed contract stay the same in all contexts +program_ext_codecopy_codesize = pytest.param( + Op.MSTORE( + 0, Op.EXTCODESIZE(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + ) + + Op.EXTCODECOPY(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0, 0, 30) + + Op.RETURN(0, 32), + ProgramResult(result=0x6001600101000000000000000000000000000000000000000000000000000005), + id="program_EXTCODECOPY_EXTCODESIZE", +) + +# Check that returndatasize stays the same in all contexts +program_returndatasize = pytest.param( + Op.MSTORE(0, Op.RETURNDATASIZE) + + Op.CALL(100000, 2, 0, 0, 10, 32, 20) + + Op.MSTORE(0, Op.ADD(Op.MLOAD(0), Op.RETURNDATASIZE)) + + Op.RETURN(0, 32), + ProgramResult(result=32, from_fork=Byzantium), + id="program_RETURNDATASIZE", +) + +# Check that returndatacopy stays the same in all contexts +program_returndatacopy = pytest.param( + Op.CALL(100000, 2, 0, 0, 10, 32, 20) + Op.RETURNDATACOPY(0, 0, 32) + Op.RETURN(0, 32), + ProgramResult( + result=0x1D448AFD928065458CF670B60F5A594D735AF0172C8D67F22A81680132681CA, + from_fork=Byzantium, + ), + id="program_RETURNDATACOPY", +) + +# Check that extcodehash stays the same in all contexts +program_extcodehash = pytest.param( + Op.MSTORE( + 0, Op.EXTCODEHASH(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + ) + + Op.RETURN(0, 32), + ProgramResult( + result=0x8C634A8B28DD46F5DCB9A9F5DA1FAED26D0FB5ED98F3873A29AD27AAAFFDE0E4, + from_fork=Constantinople, + ), + id="program_EXTCODEHASH", +) + +# Check that blockhash stays the same in all contexts +# Need a way to pre calculate at least hash of block 0 +program_blockhash = pytest.param( + # Calculate gas hash of Op.BLOCKHASH(0) value + Op.MSTORE(64, Op.BLOCKHASH(0)) + + Op.CALL( + Op.GAS, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, 0, 64, 32, 0, 0 + ) + + Op.MSTORE(32, Op.BLOCKHASH(0)) + + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.BLOCKHASH_0), + id="program_BLOCKHASH", +) + +# Need a way to pre calculate coinbase +program_coinbase = pytest.param( + Op.MSTORE(0, Op.COINBASE) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.COINBASE), + id="program_COINBASE", +) + +program_timestamp = pytest.param( + Op.MSTORE(0, Op.TIMESTAMP) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.TIMESTAMP), + id="program_TIMESTAMP", +) + +program_number = pytest.param( + Op.MSTORE(0, Op.NUMBER) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.NUMBER), + id="program_NUMBER", +) + +program_difficulty_randao = pytest.param( + # Calculate gas hash of DIFFICULTY value + Op.MSTORE(64, Op.PREVRANDAO) + + Op.CALL( + Op.GAS, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, 0, 64, 32, 0, 0 + ) + + Op.MSTORE(32, Op.PREVRANDAO) + + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.DIFFICULTY_RANDAO), + id="program_DIFFICULTY", +) + +program_gaslimit = pytest.param( + Op.MSTORE(0, Op.GASLIMIT) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.GASLIMIT), + id="program_GASLIMIT", +) + +# Check that chainid stays the same in all contexts +program_chainid = pytest.param( + Op.MSTORE(0, Op.CHAINID) + Op.RETURN(0, 32), + ProgramResult(result=1, from_fork=Istanbul), + id="program_CHAINID", +) + +# Check the SELFBALANCE in all execution contexts +program_selfbalance = pytest.param( + Op.MSTORE(0, Op.SELFBALANCE) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.SELFBALANCE, from_fork=Istanbul), + id="program_SELFBALANCE", +) + +program_basefee = pytest.param( + # Calculate gas hash of BASEFEE value + Op.MSTORE(64, Op.BASEFEE) + + Op.CALL( + Op.GAS, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, 0, 64, 32, 0, 0 + ) + + Op.MSTORE(32, Op.BASEFEE) + + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.BASEFEE, from_fork=London), + id="program_BASEFEE", +) + +program_blobhash = pytest.param( + Op.MSTORE(0, Op.BLOBHASH) + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.BLOBHASH_0, from_fork=Cancun), + id="program_BLOBHASH", +) + +program_blobbasefee = pytest.param( + # Calculate gas hash of BLOBBASEFEE value + Op.MSTORE(64, Op.BLOBBASEFEE) + + Op.CALL( + Op.GAS, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, 0, 64, 32, 0, 0 + ) + + Op.MSTORE(32, Op.BLOBBASEFEE) + + Op.RETURN(0, 32), + ProgramResult(result=ScenarioExpectOpcode.BLOBBASEFEE, from_fork=Cancun), + id="program_BLOBBASEFEE", +) + +program_tload = pytest.param( + Op.MSTORE(0, Op.TLOAD(0)) + Op.RETURN(0, 32), + ProgramResult(result=0, from_fork=Cancun), + id="program_TLOAD", +) + +program_mcopy = pytest.param( + Op.MSTORE(0, 0) + + Op.MSTORE(32, 0x000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F) + + Op.MCOPY(0, 32, 32) + + Op.RETURN(0, 32), + ProgramResult( + result=0x000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F, from_fork=Cancun + ), + id="program_MCOPY", +) + +program_push0 = pytest.param( + Op.PUSH1(10) + Op.PUSH0 + Op.MSTORE + Op.RETURN(0, 32), + ProgramResult(result=10, from_fork=Shanghai), + id="program_PUSH0", +) diff --git a/tests/frontier/scenarios/scenarios/__init__.py b/tests/frontier/scenarios/scenarios/__init__.py new file mode 100644 index 0000000000..20f2893c0e --- /dev/null +++ b/tests/frontier/scenarios/scenarios/__init__.py @@ -0,0 +1,3 @@ +""" +Scenarios common import +""" diff --git a/tests/frontier/scenarios/scenarios/call_combinations.py b/tests/frontier/scenarios/scenarios/call_combinations.py new file mode 100644 index 0000000000..f29fd8ed45 --- /dev/null +++ b/tests/frontier/scenarios/scenarios/call_combinations.py @@ -0,0 +1,300 @@ +""" +Define Scenario that will put a given program in all call contexts +""" + +from dataclasses import dataclass +from typing import List + +from ethereum_test_tools import Address +from ethereum_test_tools.vm.opcode import Opcode +from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_vm import EVMCodeType + +from ..common import Scenario, ScenarioEnvironment, ScenarioGeneratorInput + + +class ScenariosCallCombinations: + """ + Class that would generate scenarios for all call combinations + """ + + @dataclass + class AddressBalance: + """ + Definition of values we use to put in contract balances and call + """ + + root_call_value = 1 + first_call_value = 3 + second_call_value = 5 + + root_contract_balance = 105 + scenario_contract_balance = 107 + sub_contract_balance = 111 + program_selfbalance = 113 + + """The gas we keep before calling an address""" + keep_gas = 100000 + + """Possible calls list to make as a first call""" + first_calls: List[Opcode] = [] + + """Possible calls list to make as a second call""" + second_calls: List[Opcode] = [] + + """Balance map that we put in different accounts""" + balance: AddressBalance + input: ScenarioGeneratorInput + + env: ScenarioEnvironment + + def __init__(self, input: ScenarioGeneratorInput): + """ + Define possible call combinations given the fork + """ + self.first_calls = [ + callcode + for callcode, evm_type in input.fork.call_opcodes() + if evm_type == EVMCodeType.LEGACY + ] + self.second_calls = [ + callcode + for callcode, evm_type in input.fork.call_opcodes() + if evm_type == EVMCodeType.LEGACY + ] + self.second_calls.append(Op.NOOP) + self.input = input + self.balance = self.AddressBalance() + + def generate(self) -> List[Scenario]: + """ + Generate Scenarios for call combinations + We take code that we want to test at input.operation_contract + and put it in the context of call combinations. + + Example: + root_contract -> call -> scenario_contract -> first_call -> sub_contract + sub_contact -> second_call -> code + We assume that code always returns it's result + That we pass as return value in scenario_contract for the post state verification + """ + list: List[Scenario] = [] + + for first_call in self.first_calls: + for second_call in self.second_calls: + if second_call == Op.NOOP: + self._generate_one_call_scenarios(first_call, list) + else: + self._generate_two_call_scenarios(first_call, second_call, list) + return list + + def _generate_one_call_scenarios(self, first_call: Opcode, list: List[Scenario]): + """ + Generate scenario for only one call + root_contract -(CALL)-> scenario_contract -(first_call)-> operation_contract + """ + input = self.input + balance = self.balance + operation_contract = input.pre.deploy_contract( + code=input.operation_code, balance=balance.program_selfbalance + ) + + scenario_contract = input.pre.deploy_contract( + code=Op.MSTORE(32, input.external_address) + + first_call( + gas=Op.SUB(Op.GAS, self.keep_gas), + address=operation_contract, + args_offset=32, + args_size=40, + ret_size=32, + value=balance.first_call_value, + ) + + Op.RETURN(0, 32), + balance=balance.scenario_contract_balance, + ) + + root_contract = input.pre.deploy_contract( + code=Op.CALL( + gas=Op.SUB(Op.GAS, self.keep_gas), + address=scenario_contract, + ret_size=32, + ) + + Op.RETURN(0, 32), + balance=balance.root_contract_balance, + ) + + list.append( + Scenario( + name=f"scenario_{first_call}", + code=root_contract, + env=ScenarioEnvironment( + # Define address on which behalf program is executed + code_address=( + scenario_contract + if first_call == Op.CALLCODE or first_call == Op.DELEGATECALL + else operation_contract + ), + # Define code_caller for Op.CALLER + code_caller=( + root_contract if first_call == Op.DELEGATECALL else scenario_contract + ), + # Define balance for Op.BALANCE + selfbalance=( + balance.scenario_contract_balance + if first_call in [Op.DELEGATECALL, Op.CALLCODE] + else ( + balance.program_selfbalance + if first_call == Op.STATICCALL + else balance.first_call_value + balance.program_selfbalance + ) + ), + ext_balance=input.external_balance, + call_value=( + 0 + if first_call in [Op.STATICCALL, Op.DELEGATECALL] + else balance.first_call_value + ), + call_dataload_0=int(input.external_address.hex(), 16), + call_datasize=40, + ), + ) + ) + + def _generate_two_call_scenarios( + self, first_call: Opcode, second_call: Opcode, list: List[Scenario] + ): + """ + Generate scenario for two types of calls combination + root_contract -(CALL)-> scenario_contract -(first_call)-> sub_contract + sub_contract -(second_call) -> operation_contract + """ + + def _compute_code_caller() -> Address: + """ + Calculate who is the code caller in program_contract's code in given sequence + root -CALL-> scenario_contract -(first_call)-> sub_contract -(second_call)-> program + """ + code_caller: Address = root_contract + if first_call == Op.DELEGATECALL: + code_caller = scenario_contract + if second_call == Op.DELEGATECALL: + code_caller = root_contract + else: + if second_call == Op.DELEGATECALL: + code_caller = scenario_contract + else: + code_caller = sub_contract + if first_call == Op.CALLCODE: + code_caller = scenario_contract + return code_caller + + def _compute_selfbalance() -> int: + """ + Calculate the result of Op.SELFBALANCE in program scope in given sequence + root -CALL-> scenario_contract -(first_call)-> sub_contract -(second_call)-> program + """ + selfbalance: int = 0 + if second_call in [Op.CALL]: + selfbalance = second_call_value + balance.program_selfbalance + return selfbalance + if second_call in [Op.STATICCALL]: + selfbalance = balance.program_selfbalance + return selfbalance + if first_call == Op.STATICCALL and second_call in [Op.DELEGATECALL, Op.CALLCODE]: + selfbalance = balance.sub_contract_balance + if first_call in [Op.CALLCODE, Op.DELEGATECALL] and second_call in [ + Op.DELEGATECALL, + Op.CALLCODE, + ]: + selfbalance = balance.scenario_contract_balance + balance.root_call_value + if first_call == Op.CALL and second_call in [Op.DELEGATECALL, Op.CALLCODE]: + selfbalance = balance.sub_contract_balance + balance.first_call_value + if first_call == Op.STATICCALL and second_call == Op.STATICCALL: + selfbalance = balance.program_selfbalance + return selfbalance + + def _compute_callvalue() -> int: + """ + Calculate the expected callvalue in program scope given sequence: + root -CALL-> scenario_contract -(first_call)-> sub_contract -(second_call)-> program + """ + if second_call == Op.STATICCALL: + return 0 + if second_call == Op.DELEGATECALL: + if first_call == Op.STATICCALL: + return 0 + else: + if first_call == Op.DELEGATECALL: + return balance.root_call_value + else: + return balance.first_call_value + else: + return second_call_value + + input = self.input + balance = self.balance + second_call_value = balance.second_call_value if first_call != Op.STATICCALL else 0 + + operation_contract = input.pre.deploy_contract( + code=input.operation_code, balance=balance.program_selfbalance + ) + sub_contract = input.pre.deploy_contract( + code=Op.MSTORE(32, input.external_address) + + second_call( + gas=Op.SUB(Op.GAS, self.keep_gas), + address=operation_contract, + args_size=40, + args_offset=32, + ret_size=32, + value=second_call_value, + ) + + Op.RETURN(0, 32), + balance=balance.sub_contract_balance, + ) + scenario_contract = input.pre.deploy_contract( + code=first_call( + gas=Op.SUB(Op.GAS, self.keep_gas), + address=sub_contract, + ret_size=32, + value=balance.first_call_value, + ) + + Op.RETURN(0, 32), + balance=balance.scenario_contract_balance, + ) + + root_contract = input.pre.deploy_contract( + balance=balance.root_contract_balance, + code=Op.CALL( + gas=Op.SUB(Op.GAS, self.keep_gas), + address=scenario_contract, + ret_size=32, + value=balance.root_call_value, + ) + + Op.RETURN(0, 32), + ) + + list.append( + Scenario( + name=f"scenario_{first_call}_{second_call}", + code=root_contract, + env=ScenarioEnvironment( + # Define address on which behalf program is executed + code_address=( + operation_contract + if second_call not in [Op.CALLCODE, Op.DELEGATECALL] + else ( + sub_contract + if first_call not in [Op.CALLCODE, Op.DELEGATECALL] + else scenario_contract + ) + ), + # Define code_caller for Op.CALLER + code_caller=_compute_code_caller(), + selfbalance=_compute_selfbalance(), + ext_balance=input.external_balance, + call_value=_compute_callvalue(), + call_dataload_0=int(input.external_address.hex(), 16), + call_datasize=40, + ), + ) + ) diff --git a/tests/frontier/scenarios/scenarios/create_combinations.py b/tests/frontier/scenarios/scenarios/create_combinations.py new file mode 100644 index 0000000000..390bf8b41b --- /dev/null +++ b/tests/frontier/scenarios/scenarios/create_combinations.py @@ -0,0 +1,177 @@ +""" +Define Scenario that will put a given program in create contexts +""" + +from dataclasses import dataclass +from typing import List + +from ethereum_test_tools import Bytecode +from ethereum_test_tools.vm.opcode import Macros as Om +from ethereum_test_tools.vm.opcode import Opcode +from ethereum_test_tools.vm.opcode import Opcodes as Op +from ethereum_test_types import compute_create_address +from ethereum_test_vm import EVMCodeType + +from ..common import Scenario, ScenarioEnvironment, ScenarioGeneratorInput + + +@dataclass +class AddressBalance: + """ + Definition of values we use to put in contract balances and call + """ + + root_call_value = 1 + create_value = 3 + call_value = 5 + root_contract_balance = 100 + scenario_contract_balance = 200 + + +def scenarios_create_combinations(input: ScenarioGeneratorInput) -> List[Scenario]: + """Generate Scenarios for create combinations""" + + def _compute_selfbalance() -> int: + """ + Compute selfbalance opcode for root -> call -> scenario -> create | [call*] -> program + """ + if call in [Op.DELEGATECALL, Op.CALLCODE]: + return ( + balance.scenario_contract_balance + balance.root_call_value - balance.create_value + ) + if call == Op.CALL: + return balance.create_value + balance.call_value + return balance.create_value + + list: List[Scenario] = [] + keep_gas = 100000 + create_types: List[Opcode] = [ + create_code + for create_code, evm_type in input.fork.create_opcodes() + if evm_type == EVMCodeType.LEGACY + ] + env: ScenarioEnvironment + balance: AddressBalance = AddressBalance() + + # run code in create constructor + for create in create_types: + salt = [0] if create == Op.CREATE2 else [] + operation_contract = input.pre.deploy_contract(code=input.operation_code) + + # the code result in init code will be actually code of a deployed contract + scenario_contract = input.pre.deploy_contract( + balance=3, + code=Op.EXTCODECOPY(operation_contract, 0, 0, Op.EXTCODESIZE(operation_contract)) + + Op.MSTORE(0, create(3, 0, Op.EXTCODESIZE(operation_contract), *salt)) + + Op.EXTCODECOPY(Op.MLOAD(0), 0, 0, 32) + + Op.RETURN(0, 32), + ) + + created_address = compute_create_address( + address=scenario_contract, + nonce=1, + initcode=input.operation_code, + opcode=Op.CREATE if create == Op.CREATE else Op.CREATE2, + ) + + env = ScenarioEnvironment( + # Define address on which behalf program is executed + code_address=created_address, + code_caller=scenario_contract, + selfbalance=3, + ext_balance=input.external_balance, + call_value=3, + call_dataload_0=0, + call_datasize=0, + ) + list.append( + Scenario( + name=f"scenario_{create}_constructor", + code=scenario_contract, + env=env, + ) + ) + + # create a contract with test code and call it + deploy_code = Bytecode( + Op.EXTCODECOPY(operation_contract, 0, 0, Op.EXTCODESIZE(operation_contract)) + + Op.RETURN(0, Op.EXTCODESIZE(operation_contract)) + ) + deploy_code_size: int = int(len(deploy_code.hex()) / 2) + call_types: List[Opcode] = [ + callcode + for callcode, evm_type in input.fork.call_opcodes() + if evm_type == EVMCodeType.LEGACY + ] + + for create in create_types: + for call in call_types: + salt = [0] if create == Op.CREATE2 else [] + + scenario_contract = input.pre.deploy_contract( + balance=balance.scenario_contract_balance, + code=Om.MSTORE(deploy_code, 0) + + Op.MSTORE(32, create(balance.create_value, 0, deploy_code_size, *salt)) + + Op.MSTORE(0, 0) + + Op.MSTORE(64, input.external_address) + + call( + gas=Op.SUB(Op.GAS, keep_gas), + address=Op.MLOAD(32), + args_offset=64, + args_size=40, + ret_offset=0, + ret_size=32, + value=balance.call_value, + ) + + Op.RETURN(0, 32), + ) + + root_contract = input.pre.deploy_contract( + balance=balance.root_contract_balance, + code=Op.CALL( + gas=Op.SUB(Op.GAS, keep_gas), + address=scenario_contract, + ret_size=32, + value=balance.root_call_value, + ) + + Op.RETURN(0, 32), + ) + + created_address = compute_create_address( + address=scenario_contract, + nonce=1, + initcode=deploy_code, + opcode=Op.CREATE if create == Op.CREATE else Op.CREATE2, + ) + + env = ScenarioEnvironment( + # Define address on which behalf program is executed + code_address=( + scenario_contract + if call in [Op.CALLCODE, Op.DELEGATECALL] + else created_address + ), + code_caller=root_contract if call == Op.DELEGATECALL else scenario_contract, + selfbalance=_compute_selfbalance(), + ext_balance=input.external_balance, + call_value=( + 0 + if call in [Op.STATICCALL] + else ( + balance.root_call_value + if call in [Op.DELEGATECALL] + else balance.call_value + ) + ), + call_dataload_0=int(input.external_address.hex(), 16), + call_datasize=40, + ) + list.append( + Scenario( + name=f"scenario_{create}_then_{call}", + code=root_contract, + env=env, + ) + ) + + return list diff --git a/tests/frontier/scenarios/scenarios/revert_combinations.py b/tests/frontier/scenarios/scenarios/revert_combinations.py new file mode 100644 index 0000000000..ff933c2a37 --- /dev/null +++ b/tests/frontier/scenarios/scenarios/revert_combinations.py @@ -0,0 +1,46 @@ +""" +Define Scenario that will run a given program and then revert +""" + +from typing import List + +from ethereum_test_tools.vm.opcode import Macro +from ethereum_test_tools.vm.opcode import Macros as Om +from ethereum_test_tools.vm.opcode import Opcode +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from ..common import Scenario, ScenarioEnvironment, ScenarioGeneratorInput + + +def scenarios_revert_combinations(input: ScenarioGeneratorInput) -> List[Scenario]: + """Generate Scenarios for revert combinations""" + list: List[Scenario] = [] + max_scenario_gas = 100000 + # TODO stack underflow cause + revert_types: List[Opcode | Macro] = [Op.REVERT, Op.STOP, Om.OOG] + for revert in revert_types: + operation_contract = input.pre.deploy_contract(code=input.operation_code) + scenario_contract = input.pre.deploy_contract( + code=Op.CALL(gas=max_scenario_gas, address=operation_contract, ret_size=32) + + revert + + Op.RETURN(0, 32) + ) + env: ScenarioEnvironment = ScenarioEnvironment( + code_address=operation_contract, + code_caller=scenario_contract, + selfbalance=0, + ext_balance=input.external_balance, + call_value=0, + call_dataload_0=0, + call_datasize=0, + ) + list.append( + Scenario( + name=f"scenario_revert_by_{revert}", + code=scenario_contract, + env=env, + reverting=True, + ) + ) + + return list diff --git a/tests/frontier/scenarios/test_scenarios.py b/tests/frontier/scenarios/test_scenarios.py new file mode 100644 index 0000000000..62ab691249 --- /dev/null +++ b/tests/frontier/scenarios/test_scenarios.py @@ -0,0 +1,289 @@ +""" +Call every possible opcode and test that the subcall is successful +if the opcode is supported by the fork and fails otherwise. +""" + +from typing import Dict, List, Tuple + +import pytest + +from ethereum_test_base_types import Number, to_bytes +from ethereum_test_forks import Fork +from ethereum_test_tools import ( + Account, + Address, + Alloc, + Bytecode, + Conditional, + Environment, + Hash, + StateTestFiller, + Storage, + Transaction, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +from .common import ( + ExecutionEnvironment, + ProgramResult, + Scenario, + ScenarioGeneratorInput, + translate_result, +) +from .programs.all_frontier_opcodes import program_all_frontier_opcodes +from .programs.context_calls import ( + program_address, + program_balance, + program_basefee, + program_blobbasefee, + program_blobhash, + program_blockhash, + program_calldatacopy, + program_calldataload, + program_calldatasize, + program_caller, + program_callvalue, + program_chainid, + program_codecopy_codesize, + program_coinbase, + program_difficulty_randao, + program_ext_codecopy_codesize, + program_extcodehash, + program_gaslimit, + program_gasprice, + program_mcopy, + program_number, + program_origin, + program_push0, + program_returndatacopy, + program_returndatasize, + program_selfbalance, + program_timestamp, + program_tload, +) +from .scenarios.call_combinations import ScenariosCallCombinations +from .scenarios.create_combinations import scenarios_create_combinations +from .scenarios.revert_combinations import scenarios_revert_combinations + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + + +@pytest.fixture +def scenarios(fork: Fork, pre: Alloc, operation: Bytecode) -> List[Scenario]: + """ + This is the main parametrization vector + Define list of contracts that execute scenarios for a given operation + """ + list: List[Scenario] = [] + + """Deploy external address to test ext opcodes""" + external_address = pre.deploy_contract(code=Op.ADD(1, 1), balance=123) + + # Contract that spends unique amount of gas based on input + # Used for the values we can't predict, can be gas consuming on high values + # So that if we can't check exact value in expect section, we at least spend unique gas amount + gas_hash_address = pre.deploy_contract( + code=Op.MSTORE(0, 0) + + Op.JUMPDEST + + Op.CALLDATACOPY(63, Op.MLOAD(0), 1) + + Op.JUMPDEST + + Conditional( + condition=Op.EQ(Op.MLOAD(32), 0), + if_true=Op.MSTORE(0, Op.ADD(1, Op.MLOAD(0))) + + Conditional( + condition=Op.GT(Op.MLOAD(0), 32), + if_true=Op.RETURN(0, 0), + if_false=Op.JUMP(5), + ), + if_false=Op.MSTORE(32, Op.SUB(Op.MLOAD(32), 1)) + Op.JUMP(14), + ) + ) + + """Replace BALANCE(0xfff..fff) to BALANCE(external_address) in operation""" + """Replace EXTCODESIZE(0xfff..fff) to EXTCODESIZE(external_address) in operation""" + """Replace EXTCODEHASH(0xfff..fff) to EXTCODEHASH(external_address) in operation""" + """Replace EXTCODECOPY(0xfff..fff, ...) to EXTCODECOPY(external_address, ...)""" + replace_list: List[Tuple[str, str]] = [ + ( + "600060006020604060007ffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffe5af1", + Op.CALL(Op.GAS, gas_hash_address, 0, 64, 32, 0, 0).hex(), + ), + ( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31", + Op.BALANCE(external_address).hex(), + ), + ( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3b", + Op.EXTCODESIZE(external_address).hex(), + ), + ( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + Op.EXTCODEHASH(external_address).hex(), + ), + ( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3c", + "3c".join(Op.BALANCE(external_address).hex().rsplit("31", 1)), + ), + ] + for find, replace in replace_list: + if find in operation.hex(): + new_operation_hex = operation.hex().replace( + find, + replace, + ) + operation = Bytecode( + bytes_or_byte_code_base=to_bytes(new_operation_hex), + popped_stack_items=0, + pushed_stack_items=0, + ) + # raise Exception(operation.hex()) + + input: ScenarioGeneratorInput = ScenarioGeneratorInput( + fork=fork, + pre=pre, + operation_code=operation, + external_address=external_address, + external_balance=123, + ) + + call_combinations = ScenariosCallCombinations(input).generate() + for combination in call_combinations: + # if combination.name == "scenario_STATICCALL_CALL": + list.append(combination) + + call_combinations = scenarios_create_combinations(input) + for combination in call_combinations: + # if combination.name == "scenario_CREATE2_then_CALL": + list.append(combination) + + revert_combinations = scenarios_revert_combinations(input) + for combination in revert_combinations: + list.append(combination) + + """ + // 21. 0x00FD Run the code, call a contract that reverts, then run again + // 22. 0x00FE Run the code, call a contract that goes out of gas, then run again + // 23. 0x00FF Run the code, call a contract that self-destructs, then run again + // 34. 0x60BACCFA57 Call recurse to the limit + """ + + return list + + +@pytest.mark.valid_from("Frontier") +@pytest.mark.parametrize( + "operation, expected_result", + [ + # 00 - 20 + program_all_frontier_opcodes, + # 30 + program_address, + program_balance, + program_origin, + program_caller, + program_callvalue, + program_calldataload, + program_calldatasize, + program_calldatacopy, + program_codecopy_codesize, + program_gasprice, + program_ext_codecopy_codesize, + program_returndatasize, + program_returndatacopy, + program_extcodehash, + # 40 + program_blockhash, # can't use because we are in state test mode + program_coinbase, + program_timestamp, + program_number, + program_difficulty_randao, + program_gaslimit, + program_chainid, + program_selfbalance, + program_basefee, + program_blobhash, + program_blobbasefee, + program_tload, + program_mcopy, + program_push0, + ], +) +def test_scenarios( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + expected_result: ProgramResult, + scenarios, +): + """ + Test given operation in different scenarios + Verify that it's return value equal to expected result on every scenario, + that is valid for the given fork + + Note: Don't use pytest parametrize for scenario production, because scenarios will be complex + Generate one test file for [each operation] * [each scenario] to save space + As well as operations will be complex too + """ + tx_env = Environment() + tx_env.set_fork_requirements(fork) + post = Storage() + + tx_origin: Address = pre.fund_eoa() + tx_gasprice: int = 10 + exec_env = ExecutionEnvironment( + fork=fork, + origin=tx_origin, + gasprice=tx_gasprice, + timestamp=tx_env.timestamp, + number=tx_env.number, + gaslimit=tx_env.gas_limit, + coinbase=tx_env.fee_recipient, + # how to pre calculate this values: + blob_basefee=0, # because we are in frontier test folder + blobhash_0=Hash(0), # because we are in frontier test folder + blockhash_0=Hash(0), + difficulty_randao=0 if tx_env.difficulty is None else int(tx_env.difficulty.hex(), 16), + basefee=0 if tx_env.base_fee_per_gas is None else int(tx_env.base_fee_per_gas.hex(), 16), + ) + + def make_result(scenario: Scenario) -> int: + """Make Scenario post result""" + if scenario.reverting: + return post.store_next(0, hint=scenario.name) + else: + return post.store_next( + translate_result(expected_result, scenario.env, exec_env), hint=scenario.name + ) + + runner_contract = pre.deploy_contract( + code=sum( + Op.MSTORE(0, 0) + + Op.CALL(1000000, scenario.code, 0, 0, 0, 0, 32) + + Op.SSTORE(make_result(scenario), Op.MLOAD(0)) + for scenario in scenarios + ) + + Op.SSTORE(post.store_next(1), 1) + ) + + tx = Transaction( + sender=tx_origin, + gas_limit=500_000_000, + gas_price=tx_gasprice, + to=runner_contract, + data=b"", + value=0, + protected=False, + ) + + state_test( + env=tx_env, + pre=pre, + post={ + runner_contract: Account( + storage=post, + ) + }, + tx=tx, + )