diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fee1e7e740..7030cf45bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,17 +62,42 @@ jobs: strategy: matrix: python-version: [["3.11", "311"]] - # run in modes: --optimize [gas, none, codesize] opt-mode: ["gas", "none", "codesize"] + evm-version: [shanghai] debug: [true, false] - # run across other python versions.# we don't really need to run all - # modes across all python versions - one is enough + + # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#expanding-or-adding-matrix-configurations include: + # test default settings with 3.11 across all supported evm versions + - python-version: ["3.11", "311"] + debug: false + opt-mode: gas + evm-version: london + - python-version: ["3.11", "311"] + debug: false + opt-mode: gas + evm-version: paris + - python-version: ["3.11", "311"] + debug: false + opt-mode: gas + evm-version: shanghai + # enable when py-evm makes it work: + #- python-version: ["3.11", "311"] + # debug: false + # opt-mode: gas + # evm-version: cancun + + # run across other python versions. we don't really need to run all + # modes across all python versions - one is enough - python-version: ["3.10", "310"] opt-mode: gas debug: false + evm-version: shanghai + + # TODO 3.12 doesn't work yet, investigate - may be hypothesis issue + #- python-version: ["3.12", "312"] - name: py${{ matrix.python-version[1] }}-opt-${{ matrix.opt-mode }}${{ matrix.debug && '-debug' || '' }} + name: py${{ matrix.python-version[1] }}-opt-${{ matrix.opt-mode }}${{ matrix.debug && '-debug' || '' }}-${{ matrix.evm-version }} steps: - uses: actions/checkout@v4 @@ -86,11 +111,18 @@ jobs: python-version: ${{ matrix.python-version[0] }} cache: "pip" - - name: Install Tox - run: pip install tox - - - name: Run Tox - run: TOXENV=py${{ matrix.python-version[1] }} tox -r -- --optimize ${{ matrix.opt-mode }} ${{ matrix.debug && '--enable-compiler-debug-mode' || '' }} -r aR tests/ + - name: Install dependencies + run: pip install .[test] + + - name: Run tests + run: | + pytest \ + -m "not fuzzing" \ + --optimize ${{ matrix.opt-mode }} \ + --evm-version ${{ matrix.evm-version }} \ + ${{ matrix.debug && '--enable-compiler-debug-mode' || '' }} \ + --showlocals -r aR \ + tests/ - name: Upload Coverage uses: codecov/codecov-action@v4 diff --git a/tests/conftest.py b/tests/conftest.py index 54c8549867..2e5f11b9b8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,6 +15,7 @@ from web3.contract import Contract from web3.providers.eth_tester import EthereumTesterProvider +import vyper.evm.opcodes as evm from tests.utils import working_directory from vyper import compiler from vyper.ast.grammar import parse_vyper_source @@ -38,7 +39,7 @@ def set_evm_verbose_logging(): - logger = logging.getLogger("eth.vm.computation.Computation") + logger = logging.getLogger("eth.vm.computation.BaseComputation") setup_DEBUG2_logging() logger.setLevel("DEBUG2") @@ -59,6 +60,13 @@ def pytest_addoption(parser): ) parser.addoption("--enable-compiler-debug-mode", action="store_true") + parser.addoption( + "--evm-version", + choices=list(evm.EVM_VERSIONS.keys()), + default="shanghai", + help="set evm version", + ) + @pytest.fixture(scope="module") def output_formats(): @@ -81,6 +89,18 @@ def debug(pytestconfig): _set_debug_mode(debug) +@pytest.fixture(scope="session", autouse=True) +def evm_version(pytestconfig): + # note: we configure the evm version that we emit code for, + # but eth-tester is only configured with the latest mainnet + # version. + evm_version_str = pytestconfig.getoption("evm_version") + evm.DEFAULT_EVM_VERSION = evm_version_str + # this should get overridden by anchor_evm_version, + # but set it anyway + evm.active_evm_version = evm.EVM_VERSIONS[evm_version_str] + + @pytest.fixture def chdir_tmp_path(tmp_path): # this is useful for when you want imports to have relpaths @@ -308,7 +328,6 @@ def _get_contract( **kwargs, ): settings = Settings() - settings.evm_version = kwargs.pop("evm_version", None) settings.optimize = override_opt_level or optimize out = compiler.compile_code( source_code, @@ -383,7 +402,6 @@ def _deploy_blueprint_for( w3, source_code, optimize, output_formats, initcode_prefix=ERC5202_PREFIX, **kwargs ): settings = Settings() - settings.evm_version = kwargs.pop("evm_version", None) settings.optimize = optimize out = compiler.compile_code( source_code, diff --git a/tests/functional/codegen/features/test_clampers.py b/tests/functional/codegen/features/test_clampers.py index c028805c6a..578413a8f4 100644 --- a/tests/functional/codegen/features/test_clampers.py +++ b/tests/functional/codegen/features/test_clampers.py @@ -4,7 +4,6 @@ from eth.codecs import abi from eth_utils import keccak -from vyper.evm.opcodes import EVM_VERSIONS from vyper.utils import int_bounds @@ -83,9 +82,8 @@ def get_foo() -> Bytes[3]: get_contract_with_gas_estimation(clamper_test_code, *[b"cats"]) -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("n", list(range(1, 33))) -def test_bytes_m_clamper_passing(w3, get_contract, n, evm_version): +def test_bytes_m_clamper_passing(w3, get_contract, n): values = [b"\xff" * (i + 1) for i in range(n)] code = f""" @@ -94,15 +92,14 @@ def foo(s: bytes{n}) -> bytes{n}: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) for v in values: v = v.ljust(n, b"\x00") assert c.foo(v) == v -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("n", list(range(1, 32))) # bytes32 always passes -def test_bytes_m_clamper_failing(w3, get_contract, tx_failed, n, evm_version): +def test_bytes_m_clamper_failing(w3, get_contract, tx_failed, n): values = [] values.append(b"\x00" * n + b"\x80") # just one bit set values.append(b"\xff" * n + b"\x80") # n*8 + 1 bits set @@ -118,7 +115,7 @@ def foo(s: bytes{n}) -> bytes{n}: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) for v in values: # munge for `_make_tx` with tx_failed(): @@ -126,9 +123,8 @@ def foo(s: bytes{n}) -> bytes{n}: _make_tx(w3, c.address, f"foo(bytes{n})", [int_value]) -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("n", list(range(32))) -def test_sint_clamper_passing(w3, get_contract, n, evm_version): +def test_sint_clamper_passing(w3, get_contract, n): bits = 8 * (n + 1) lo, hi = int_bounds(True, bits) values = [-1, 0, 1, lo, hi] @@ -138,14 +134,13 @@ def foo(s: int{bits}) -> int{bits}: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) for v in values: assert c.foo(v) == v -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("n", list(range(31))) # int256 does not clamp -def test_sint_clamper_failing(w3, tx_failed, get_contract, n, evm_version): +def test_sint_clamper_failing(w3, tx_failed, get_contract, n): bits = 8 * (n + 1) lo, hi = int_bounds(True, bits) values = [-(2**255), 2**255 - 1, lo - 1, hi + 1] @@ -155,42 +150,39 @@ def foo(s: int{bits}) -> int{bits}: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) for v in values: with tx_failed(): _make_tx(w3, c.address, f"foo(int{bits})", [v]) -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("value", [True, False]) -def test_bool_clamper_passing(w3, get_contract, value, evm_version): +def test_bool_clamper_passing(w3, get_contract, value): code = """ @external def foo(s: bool) -> bool: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) assert c.foo(value) == value -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("value", [2, 3, 4, 8, 16, 2**256 - 1]) -def test_bool_clamper_failing(w3, tx_failed, get_contract, value, evm_version): +def test_bool_clamper_failing(w3, tx_failed, get_contract, value): code = """ @external def foo(s: bool) -> bool: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) with tx_failed(): _make_tx(w3, c.address, "foo(bool)", [value]) -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("value", [0] + [2**i for i in range(5)]) -def test_flag_clamper_passing(w3, get_contract, value, evm_version): +def test_flag_clamper_passing(w3, get_contract, value): code = """ flag Roles: USER @@ -204,13 +196,12 @@ def foo(s: Roles) -> Roles: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) assert c.foo(value) == value -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("value", [2**i for i in range(5, 256)]) -def test_flag_clamper_failing(w3, tx_failed, get_contract, value, evm_version): +def test_flag_clamper_failing(w3, tx_failed, get_contract, value): code = """ flag Roles: USER @@ -224,14 +215,13 @@ def foo(s: Roles) -> Roles: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) with tx_failed(): _make_tx(w3, c.address, "foo(uint256)", [value]) -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("n", list(range(32))) -def test_uint_clamper_passing(w3, get_contract, evm_version, n): +def test_uint_clamper_passing(w3, get_contract, n): bits = 8 * (n + 1) values = [0, 1, 2**bits - 1] code = f""" @@ -240,14 +230,13 @@ def foo(s: uint{bits}) -> uint{bits}: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) for v in values: assert c.foo(v) == v -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("n", list(range(31))) # uint256 has no failing cases -def test_uint_clamper_failing(w3, tx_failed, get_contract, evm_version, n): +def test_uint_clamper_failing(w3, tx_failed, get_contract, n): bits = 8 * (n + 1) values = [-1, -(2**255), 2**bits] code = f""" @@ -255,13 +244,12 @@ def test_uint_clamper_failing(w3, tx_failed, get_contract, evm_version, n): def foo(s: uint{bits}) -> uint{bits}: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) for v in values: with tx_failed(): _make_tx(w3, c.address, f"foo(uint{bits})", [v]) -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize( "value,expected", [ @@ -276,32 +264,30 @@ def foo(s: uint{bits}) -> uint{bits}: ), ], ) -def test_address_clamper_passing(w3, get_contract, value, expected, evm_version): +def test_address_clamper_passing(w3, get_contract, value, expected): code = """ @external def foo(s: address) -> address: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) assert c.foo(value) == expected -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("value", [2**160, 2**256 - 1]) -def test_address_clamper_failing(w3, tx_failed, get_contract, value, evm_version): +def test_address_clamper_failing(w3, tx_failed, get_contract, value): code = """ @external def foo(s: address) -> address: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) with tx_failed(): _make_tx(w3, c.address, "foo(address)", [value]) -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize( "value", [ @@ -321,19 +307,18 @@ def foo(s: address) -> address: "-18707220957835557353007165858768422651595.9365500928", # -2**167 ], ) -def test_decimal_clamper_passing(get_contract, value, evm_version): +def test_decimal_clamper_passing(get_contract, value): code = """ @external def foo(s: decimal) -> decimal: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) assert c.foo(Decimal(value)) == Decimal(value) -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize( "value", [ @@ -343,14 +328,14 @@ def foo(s: decimal) -> decimal: -187072209578355573530071658587684226515959365500929, # - (2 ** 127 - 1e-10) ], ) -def test_decimal_clamper_failing(w3, tx_failed, get_contract, value, evm_version): +def test_decimal_clamper_failing(w3, tx_failed, get_contract, value): code = """ @external def foo(s: decimal) -> decimal: return s """ - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) with tx_failed(): _make_tx(w3, c.address, "foo(fixed168x10)", [value]) diff --git a/tests/functional/codegen/features/test_transient.py b/tests/functional/codegen/features/test_transient.py index 718f5ae314..675055f8cf 100644 --- a/tests/functional/codegen/features/test_transient.py +++ b/tests/functional/codegen/features/test_transient.py @@ -1,35 +1,30 @@ import pytest from vyper.compiler import compile_code -from vyper.compiler.settings import Settings -from vyper.evm.opcodes import EVM_VERSIONS +from vyper.evm.opcodes import version_check from vyper.exceptions import StructureException -post_cancun = {k: v for k, v in EVM_VERSIONS.items() if v >= EVM_VERSIONS["cancun"]} - -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS.keys())) def test_transient_blocked(evm_version): # test transient is blocked on pre-cancun and compiles post-cancun code = """ my_map: transient(HashMap[address, uint256]) """ - settings = Settings(evm_version=evm_version) - if EVM_VERSIONS[evm_version] >= EVM_VERSIONS["cancun"]: - assert compile_code(code, settings=settings) is not None + if version_check(begin="cancun"): + assert compile_code(code) is not None else: with pytest.raises(StructureException): - compile_code(code, settings=settings) + compile_code(code) + +def test_transient_compiles(): + if not version_check(begin="cancun"): + pytest.skip("transient storage will not compile, pre-cancun") -@pytest.mark.parametrize("evm_version", list(post_cancun.keys())) -def test_transient_compiles(evm_version): - # test transient keyword at least generates TLOAD/TSTORE opcodes - settings = Settings(evm_version=evm_version) getter_code = """ my_map: public(transient(HashMap[address, uint256])) """ - t = compile_code(getter_code, settings=settings, output_formats=["opcodes_runtime"]) + t = compile_code(getter_code, output_formats=["opcodes_runtime"]) t = t["opcodes_runtime"].split(" ") assert "TLOAD" in t @@ -42,7 +37,7 @@ def test_transient_compiles(evm_version): def setter(k: address, v: uint256): self.my_map[k] = v """ - t = compile_code(setter_code, settings=settings, output_formats=["opcodes_runtime"]) + t = compile_code(setter_code, output_formats=["opcodes_runtime"]) t = t["opcodes_runtime"].split(" ") assert "TLOAD" not in t @@ -55,7 +50,7 @@ def setter(k: address, v: uint256): def setter(k: address, v: uint256): self.my_map[k] = v """ - t = compile_code(getter_setter_code, settings=settings, output_formats=["opcodes_runtime"]) + t = compile_code(getter_setter_code, output_formats=["opcodes_runtime"]) t = t["opcodes_runtime"].split(" ") assert "TLOAD" in t diff --git a/tests/functional/syntax/test_chainid.py b/tests/functional/syntax/test_chainid.py index ff8473f1a2..3ff540f212 100644 --- a/tests/functional/syntax/test_chainid.py +++ b/tests/functional/syntax/test_chainid.py @@ -1,23 +1,8 @@ import pytest from vyper import compiler -from vyper.compiler.settings import Settings -from vyper.evm.opcodes import EVM_VERSIONS from vyper.exceptions import TypeMismatch - -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) -def test_evm_version(evm_version): - code = """ -@external -def foo(): - a: uint256 = chain.id - """ - settings = Settings(evm_version=evm_version) - - assert compiler.compile_code(code, settings=settings) is not None - - fail_list = [ ( """ diff --git a/tests/functional/syntax/test_codehash.py b/tests/functional/syntax/test_codehash.py index 8aada22da7..d351981946 100644 --- a/tests/functional/syntax/test_codehash.py +++ b/tests/functional/syntax/test_codehash.py @@ -1,13 +1,9 @@ -import pytest - from vyper.compiler import compile_code from vyper.compiler.settings import Settings -from vyper.evm.opcodes import EVM_VERSIONS from vyper.utils import keccak256 -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) -def test_get_extcodehash(get_contract, evm_version, optimize): +def test_get_extcodehash(get_contract, optimize): code = """ a: address @@ -32,12 +28,12 @@ def foo3() -> bytes32: def foo4() -> bytes32: return self.a.codehash """ - settings = Settings(evm_version=evm_version, optimize=optimize) + settings = Settings(optimize=optimize) compiled = compile_code(code, output_formats=["bytecode_runtime"], settings=settings) bytecode = bytes.fromhex(compiled["bytecode_runtime"][2:]) hash_ = keccak256(bytecode) - c = get_contract(code, evm_version=evm_version) + c = get_contract(code) assert c.foo(c.address) == hash_ assert not int(c.foo("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF").hex(), 16) diff --git a/tests/functional/syntax/test_self_balance.py b/tests/functional/syntax/test_self_balance.py index 28cbd05453..4f844088f6 100644 --- a/tests/functional/syntax/test_self_balance.py +++ b/tests/functional/syntax/test_self_balance.py @@ -1,12 +1,7 @@ -import pytest - from vyper import compiler -from vyper.compiler.settings import Settings -from vyper.evm.opcodes import EVM_VERSIONS -@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) -def test_self_balance(w3, get_contract_with_gas_estimation, evm_version): +def test_self_balance(w3, get_contract_with_gas_estimation): code = """ @external @view @@ -19,11 +14,10 @@ def get_balance() -> uint256: def __default__(): pass """ - settings = Settings(evm_version=evm_version) - opcodes = compiler.compile_code(code, output_formats=["opcodes"], settings=settings)["opcodes"] + opcodes = compiler.compile_code(code, output_formats=["opcodes"])["opcodes"] assert "SELFBALANCE" in opcodes - c = get_contract_with_gas_estimation(code, evm_version=evm_version) + c = get_contract_with_gas_estimation(code) w3.eth.send_transaction({"to": c.address, "value": 1337}) assert c.get_balance() == 1337 diff --git a/tests/unit/compiler/ir/test_compile_ir.py b/tests/unit/compiler/ir/test_compile_ir.py index 706c31e0f2..ba85297afb 100644 --- a/tests/unit/compiler/ir/test_compile_ir.py +++ b/tests/unit/compiler/ir/test_compile_ir.py @@ -1,6 +1,7 @@ import pytest from vyper.codegen.ir_node import IRnode +from vyper.evm.opcodes import version_check from vyper.ir import compile_ir from vyper.ir.s_expressions import parse_s_exp @@ -68,4 +69,9 @@ def test_pc_debugger(): debugger_ir = ["seq", ["mstore", 0, 32], ["pc_debugger"]] ir_nodes = IRnode.from_list(debugger_ir) _, line_number_map = compile_ir.assembly_to_evm(compile_ir.compile_to_assembly(ir_nodes)) - assert line_number_map["pc_breakpoints"][0] == 4 + if version_check(begin="shanghai"): + offset = 4 # push0 saves a byte + else: + offset = 5 + + assert line_number_map["pc_breakpoints"][0] == offset diff --git a/tox.ini b/tox.ini index 7d8e55e312..40b08f2d5c 100644 --- a/tox.ini +++ b/tox.ini @@ -3,17 +3,6 @@ envlist = py{310,311} docs -[testenv] -usedevelop = True -commands = - pytest -m "not fuzzing" --showlocals {posargs:tests/} -basepython = - py310: python3.10 - py311: python3.11 -extras = - test -whitelist_externals = make - [testenv:docs] basepython=python3 deps = diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py index 2ba8a5417c..778d68b5b1 100755 --- a/vyper/cli/vyper_compile.py +++ b/vyper/cli/vyper_compile.py @@ -8,6 +8,7 @@ import vyper import vyper.codegen.ir_node as ir_node +import vyper.evm.opcodes as evm from vyper.cli import vyper_json from vyper.compiler.input_bundle import FileInput, FilesystemInputBundle from vyper.compiler.settings import ( @@ -16,7 +17,6 @@ Settings, _set_debug_mode, ) -from vyper.evm.opcodes import DEFAULT_EVM_VERSION, EVM_VERSIONS from vyper.typing import ContractPath, OutputFormats T = TypeVar("T") @@ -106,9 +106,9 @@ def _parse_args(argv): ) parser.add_argument( "--evm-version", - help=f"Select desired EVM version (default {DEFAULT_EVM_VERSION}). " + help=f"Select desired EVM version (default {evm.DEFAULT_EVM_VERSION}). " "note: cancun support is EXPERIMENTAL", - choices=list(EVM_VERSIONS), + choices=list(evm.EVM_VERSIONS), dest="evm_version", ) parser.add_argument("--no-optimize", help="Do not optimize", action="store_true") diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py index 9297f9e3c3..ee909a57d4 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -8,7 +8,7 @@ from vyper.compiler.input_bundle import FileInput, InputBundle, PathLike from vyper.compiler.phases import CompilerData from vyper.compiler.settings import Settings -from vyper.evm.opcodes import DEFAULT_EVM_VERSION, anchor_evm_version +from vyper.evm.opcodes import anchor_evm_version from vyper.typing import ContractPath, OutputFormats, StorageLayout OUTPUT_FORMATS = {