diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e07bf5fa71..954ce0f2c4 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -35,6 +35,7 @@ Test fixtures for use by clients are available for each release on the [Github r - ✨ Added a new flag `--solc-version` to the `fill` command, which allows the user to specify the version of the Solidity compiler to use when compiling Yul source code; this version will now be automatically downloaded by `fill` via [`solc-select`](https://github.com/crytic/solc-select) ([#772](https://github.com/ethereum/execution-spec-tests/pull/772)). - 🐞 Fix usage of multiple `@pytest.mark.with_all*` markers which shared parameters, such as `with_all_call_opcodes` and `with_all_create_opcodes` which shared `evm_code_type`, and now only parametrize compatible values ([#762](https://github.com/ethereum/execution-spec-tests/pull/762)). - ✨ Added `selector` and `marks` fields to all `@pytest.mark.with_all*` markers, which allows passing lambda functions to select or mark specific parametrized values (see [documentation](https://ethereum.github.io/execution-spec-tests/main/writing_tests/test_markers/#covariant-marker-keyword-arguments) for more information) ([#762](https://github.com/ethereum/execution-spec-tests/pull/762)). +- ✨ Added `--feature` flag to the `fill` command, which allows the user to enable features for all tests during filling (features currently supported: `eip_7702`, `eof_v1`) ([#781](https://github.com/ethereum/execution-spec-tests/pull/781)). ### 🔧 EVM Tools diff --git a/docs/library/pytest_plugins/features.md b/docs/library/pytest_plugins/features.md new file mode 100644 index 0000000000..ee16b37aa5 --- /dev/null +++ b/docs/library/pytest_plugins/features.md @@ -0,0 +1,3 @@ +# Features Plugin + +::: pytest_plugins.features diff --git a/docs/library/pytest_plugins/navigation.md b/docs/library/pytest_plugins/navigation.md index 995ff2ed10..7c45434267 100644 --- a/docs/library/pytest_plugins/navigation.md +++ b/docs/library/pytest_plugins/navigation.md @@ -2,4 +2,5 @@ * [Overview](index.md) * [Forks](forks.md) * [Test Filler](filler.md) +* [Features](features.md) * [Spec Version Checker](spec_version_checker.md) diff --git a/docs/navigation.md b/docs/navigation.md index 14ff9393e6..153fe254db 100644 --- a/docs/navigation.md +++ b/docs/navigation.md @@ -45,3 +45,4 @@ * [EVM Transition Tool Package](library/evm_transition_tool.md) * [Pytest Plugins](library/pytest_plugins/index.md) * [Filler Plugin](library/pytest_plugins/filler.md) + * [Features Plugin](library/pytest_plugins/features.md) diff --git a/pytest.ini b/pytest.ini index b0e8832c53..34e1b7259f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,6 +7,7 @@ markers = slow pre_alloc_modify addopts = + -p pytest_plugins.features.features -p pytest_plugins.filler.pre_alloc -p pytest_plugins.filler.solc -p pytest_plugins.filler.filler diff --git a/src/cli/tests/test_pytest_fill_command.py b/src/cli/tests/test_pytest_fill_command.py index c64bdbfeaf..2a03833327 100644 --- a/src/cli/tests/test_pytest_fill_command.py +++ b/src/cli/tests/test_pytest_fill_command.py @@ -28,7 +28,7 @@ def test_fill_help(runner): assert result.exit_code == pytest.ExitCode.OK assert "[--evm-bin EVM_BIN]" in result.output assert "[--traces]" in result.output - assert "[--evm-code-type EVM_CODE_TYPE]" in result.output + assert "--feature" in result.output assert "--help" in result.output assert "Arguments defining evm executable behavior:" in result.output diff --git a/src/pytest_plugins/features/__init__.py b/src/pytest_plugins/features/__init__.py new file mode 100644 index 0000000000..9a196c4843 --- /dev/null +++ b/src/pytest_plugins/features/__init__.py @@ -0,0 +1,7 @@ +""" +Pytest plugin to enable feature-based modification during test filling/execution. +""" + +from .features import Features + +__all__ = ["Features"] diff --git a/src/pytest_plugins/features/features.py b/src/pytest_plugins/features/features.py new file mode 100644 index 0000000000..ce6d53a0b5 --- /dev/null +++ b/src/pytest_plugins/features/features.py @@ -0,0 +1,84 @@ +""" +Enable feature-specific functionality on all tests during filling/execution. +""" + +from enum import Flag, auto + +import pytest + +from ethereum_test_vm import EVMCodeType + + +class Features(Flag): + """ + Enumerates the available features. + + Each feature can be passed to the parameter flag `--feature` as a (case-insensitive) string, + and multiple features can be enabled by repeating the flag. + """ + + NONE = 0 + EIP_7702 = auto() + """ + Enables tests to be filled and executed in EIP-7702 mode, i.e. addresses returned by + `pre.deploy_contract` is not the contract itself but an EOA account that delegates + to the contract. + """ + EOF_V1 = auto() + """ + Enables tests to be filled and executed in EOF V1 mode, i.e. all test contracts deployed during + tests are automatically wrapped in an EOF V1 container. + """ + + def __str__(self): + """ + Returns the string representation of the feature. + """ + return self.name + + +def pytest_addoption(parser: pytest.Parser): + """ + Adds command-line options to pytest. + """ + features_group = parser.getgroup( + "features", "Arguments related to enabling specific testing of features" + ) + + features_group.addoption( + "--feature", + action="append", + default=[], + help="Enable a feature (may be specified multiple times, once per feature). " + "Supported features: " + ", ".join(str(feature).lower() for feature in Features), + ) + + +@pytest.fixture(scope="session") +def features(request): + """ + Returns the enabled features. + """ + features_str_list = request.config.getoption("feature") + features = Features.NONE + for feature_str in features_str_list: + features |= Features[feature_str.upper()] + return features + + +@pytest.fixture(scope="session") +def eip_7702_feature(features: Features) -> bool: + """ + Returns whether the EIP-7702 mode is enabled. + """ + return Features.EIP_7702 in features + + +@pytest.fixture(autouse=True, scope="session") +def evm_code_type(features: Features) -> EVMCodeType | None: + """ + Returns the default EVM code type for all tests. + """ + if Features.EOF_V1 in features: + return EVMCodeType.EOF_V1 + return None diff --git a/src/pytest_plugins/filler/pre_alloc.py b/src/pytest_plugins/filler/pre_alloc.py index 72da49a52c..55e7836cad 100644 --- a/src/pytest_plugins/filler/pre_alloc.py +++ b/src/pytest_plugins/filler/pre_alloc.py @@ -39,15 +39,10 @@ def pytest_addoption(parser: pytest.Parser): """ Adds command-line options to pytest. """ - pre_alloc_group = parser.getgroup("pre_alloc", "Arguments defining pre-allocation behavior.") - - pre_alloc_group.addoption( - "--strict-alloc", - action="store_true", - dest="strict_alloc", - default=False, - help=("[DEBUG ONLY] Disallows deploying a contract in a predefined address."), + pre_alloc_group = parser.getgroup( + "pre_alloc", "Arguments defining pre-allocation behavior during test filling" ) + pre_alloc_group.addoption( "--ca-start", "--contract-address-start", @@ -67,13 +62,11 @@ def pytest_addoption(parser: pytest.Parser): help="The address increment value to each deployed contract by a test.", ) pre_alloc_group.addoption( - "--evm-code-type", - action="store", - dest="evm_code_type", - default=None, - type=EVMCodeType, - choices=list(EVMCodeType), - help="Type of EVM code to deploy in each test by default.", + "--strict-alloc", + action="store_true", + dest="strict_alloc", + default=False, + help=("[DEBUG ONLY] Disallows deploying a contract in a predefined address."), ) @@ -86,6 +79,9 @@ class AllocMode(IntEnum): STRICT = 1 +SET_CODE_DELEGATION_DESIGNATION = bytes.fromhex("ef0100") + + class Alloc(BaseAlloc): """ Allocation of accounts in the state, pre and post test execution. @@ -95,6 +91,7 @@ class Alloc(BaseAlloc): _contract_address_iterator: Iterator[Address] = PrivateAttr(...) _eoa_iterator: Iterator[EOA] = PrivateAttr(...) _evm_code_type: EVMCodeType | None = PrivateAttr(None) + _eip_7702_mode: bool = PrivateAttr(False) def __init__( self, @@ -103,6 +100,7 @@ def __init__( contract_address_iterator: Iterator[Address], eoa_iterator: Iterator[EOA], evm_code_type: EVMCodeType | None = None, + eip_7702_feature: bool = False, **kwargs, ): """ @@ -113,6 +111,7 @@ def __init__( self._contract_address_iterator = contract_address_iterator self._eoa_iterator = eoa_iterator self._evm_code_type = evm_code_type + self._eip_7702_mode = eip_7702_feature def __setitem__(self, address: Address | FixedSizeBytesConvertible, account: Account | None): """ @@ -164,15 +163,37 @@ def deploy_contract( if self._alloc_mode == AllocMode.STRICT: assert Number(nonce) >= 1, "impossible to deploy contract with nonce lower than one" - super().__setitem__( - contract_address, - Account( + if self._eip_7702_mode: + super().__setitem__( + contract_address, + Account( + nonce=nonce, + balance=0, + code=self.code_pre_processor(code, evm_code_type=evm_code_type), + storage={}, + ), + ) + + eoa_set_code_proxy = next(self._eoa_iterator) + account = Account( nonce=nonce, balance=balance, - code=self.code_pre_processor(code, evm_code_type=evm_code_type), + code=SET_CODE_DELEGATION_DESIGNATION + bytes(contract_address), storage=storage, - ), - ) + ) + super().__setitem__(eoa_set_code_proxy, account) + contract_address = eoa_set_code_proxy + else: + super().__setitem__( + contract_address, + Account( + nonce=nonce, + balance=balance, + code=self.code_pre_processor(code, evm_code_type=evm_code_type), + storage=storage, + ), + ) + if label is None: # Try to deduce the label from the code frame = inspect.currentframe() @@ -212,7 +233,7 @@ def fund_eoa( account = Account( nonce=1, balance=amount, - storage=storage, + storage=storage if storage is not None else {}, ) eoa.nonce = Number(1) @@ -290,31 +311,22 @@ def eoa_iterator() -> Iterator[EOA]: return iter(eoa_by_index(i).copy() for i in count()) -@pytest.fixture(autouse=True) -def evm_code_type(request: pytest.FixtureRequest) -> EVMCodeType: - """ - Returns the default EVM code type for all tests (LEGACY). - """ - parameter_evm_code_type = request.config.getoption("evm_code_type") - if parameter_evm_code_type is not None: - assert type(parameter_evm_code_type) is EVMCodeType, "Invalid EVM code type" - return parameter_evm_code_type - return EVMCodeType.LEGACY - - @pytest.fixture(scope="function") def pre( alloc_mode: AllocMode, contract_address_iterator: Iterator[Address], eoa_iterator: Iterator[EOA], - evm_code_type: EVMCodeType, + evm_code_type: EVMCodeType | None, + eip_7702_feature: bool, ) -> Alloc: """ Returns the default pre allocation for all tests (Empty alloc). """ + # TODO: Compare `eip_7702_feature` against the current fork. return Alloc( alloc_mode=alloc_mode, contract_address_iterator=contract_address_iterator, eoa_iterator=eoa_iterator, evm_code_type=evm_code_type, + eip_7702_feature=eip_7702_feature, ) diff --git a/src/pytest_plugins/filler/tests/test_pre_alloc.py b/src/pytest_plugins/filler/tests/test_pre_alloc.py index e144fb597e..49d752256a 100644 --- a/src/pytest_plugins/filler/tests/test_pre_alloc.py +++ b/src/pytest_plugins/filler/tests/test_pre_alloc.py @@ -11,6 +11,7 @@ from ..pre_alloc import ( CONTRACT_ADDRESS_INCREMENTS_DEFAULT, CONTRACT_START_ADDRESS_DEFAULT, + SET_CODE_DELEGATION_DESIGNATION, Alloc, AllocMode, contract_address_iterator, @@ -27,23 +28,30 @@ pytest.mark.parametrize("contract_start_address", [CONTRACT_START_ADDRESS_DEFAULT]), pytest.mark.parametrize("contract_address_increments", [CONTRACT_ADDRESS_INCREMENTS_DEFAULT]), pytest.mark.parametrize("evm_code_type", [EVMCodeType.LEGACY, EVMCodeType.EOF_V1]), + pytest.mark.parametrize("eip_7702_feature", [False, True]), ] -def test_alloc_deploy_contract(pre: Alloc, evm_code_type: EVMCodeType): +def test_alloc_deploy_contract(pre: Alloc, evm_code_type: EVMCodeType, eip_7702_feature: bool): """ Test `Alloc.deploy_contract` functionallity. """ contract_1 = pre.deploy_contract(Op.SSTORE(0, 1) + Op.STOP) contract_2 = pre.deploy_contract(Op.SSTORE(0, 2) + Op.STOP) - assert contract_1 == Address(CONTRACT_START_ADDRESS_DEFAULT) - assert contract_2 == Address( - CONTRACT_START_ADDRESS_DEFAULT + CONTRACT_ADDRESS_INCREMENTS_DEFAULT - ) + if eip_7702_feature: + assert contract_1 == TestAddress + assert contract_2 == TestAddress2 + else: + assert contract_1 == Address(CONTRACT_START_ADDRESS_DEFAULT) + assert contract_2 == Address( + CONTRACT_START_ADDRESS_DEFAULT + CONTRACT_ADDRESS_INCREMENTS_DEFAULT + ) assert contract_1 in pre assert contract_2 in pre - pre_contract_1_account = pre[contract_1] - pre_contract_2_account = pre[contract_2] + pre_contract_1_account = pre[Address(CONTRACT_START_ADDRESS_DEFAULT)] + pre_contract_2_account = pre[ + Address(CONTRACT_START_ADDRESS_DEFAULT + CONTRACT_ADDRESS_INCREMENTS_DEFAULT) + ] assert pre_contract_1_account is not None assert pre_contract_2_account is not None if evm_code_type == EVMCodeType.LEGACY: @@ -58,6 +66,17 @@ def test_alloc_deploy_contract(pre: Alloc, evm_code_type: EVMCodeType): b"\xef\x00\x01\x01\x00\x04\x02\x00\x01\x00\x06\x04\x00\x00\x00\x00\x80\x00" + b"\x02`\x02`\x00U\x00" ) + if eip_7702_feature: + eoa_set_code_1 = pre[contract_1] + eoa_set_code_2 = pre[contract_2] + assert eoa_set_code_1 is not None + assert eoa_set_code_2 is not None + assert eoa_set_code_1.code == SET_CODE_DELEGATION_DESIGNATION + Address( + CONTRACT_START_ADDRESS_DEFAULT + ) + assert eoa_set_code_2.code == SET_CODE_DELEGATION_DESIGNATION + Address( + CONTRACT_START_ADDRESS_DEFAULT + CONTRACT_ADDRESS_INCREMENTS_DEFAULT + ) def test_alloc_fund_sender(pre: Alloc): diff --git a/src/pytest_plugins/help/help.py b/src/pytest_plugins/help/help.py index 34e4fdbcd0..413511b920 100644 --- a/src/pytest_plugins/help/help.py +++ b/src/pytest_plugins/help/help.py @@ -51,6 +51,7 @@ def show_test_help(config): "filler location", "defining debug", "pre-allocation behavior", + "testing of features", ] elif pytest_ini.name in [ "pytest-consume.ini",