From 390a92d7af9af91b7664173b894948c4a5fac417 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 6 May 2024 10:43:46 +0200 Subject: [PATCH 1/2] Expect ExecutionReverted error by default --- tests/conftest.py | 4 ++-- tests/evm_backends/base_env.py | 1 + tests/evm_backends/pyevm_env.py | 1 + tests/evm_backends/revm_env.py | 1 + .../builtins/codegen/test_abi_decode.py | 17 ++++++++++------- .../calling_convention/test_default_function.py | 5 ++++- .../codegen/features/decorators/test_payable.py | 3 +++ .../codegen/features/test_assert_unreachable.py | 17 ++++++++++------- .../market_maker/test_on_chain_market_maker.py | 2 +- .../test_safe_remote_purchase.py | 6 +++--- 10 files changed, 36 insertions(+), 21 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2b645fc50d..420e1224ff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ from hexbytes import HexBytes import vyper.evm.opcodes as evm_opcodes -from tests.evm_backends.base_env import BaseEnv, EvmError +from tests.evm_backends.base_env import BaseEnv, ExecutionReverted from tests.evm_backends.pyevm_env import PyEvmEnv from tests.evm_backends.revm_env import RevmEnv from tests.utils import working_directory @@ -314,7 +314,7 @@ def assert_side_effects_invoked(side_effects_contract, side_effects_trigger, n=1 @pytest.fixture(scope="module") def tx_failed(env): @contextmanager - def fn(exception=EvmError, exc_text=None): + def fn(exception=ExecutionReverted, exc_text=None): with pytest.raises(exception) as excinfo: yield diff --git a/tests/evm_backends/base_env.py b/tests/evm_backends/base_env.py index 3cefe50018..0e34d78d92 100644 --- a/tests/evm_backends/base_env.py +++ b/tests/evm_backends/base_env.py @@ -45,6 +45,7 @@ class BaseEnv: """ INVALID_OPCODE_ERROR = "NotImplemented" # must be implemented by subclasses + OUT_OF_GAS_ERROR = "NotImplemented" # must be implemented by subclasses DEFAULT_CHAIN_ID = 1 def __init__(self, gas_limit: int, account_keys: list[PrivateKey]) -> None: diff --git a/tests/evm_backends/pyevm_env.py b/tests/evm_backends/pyevm_env.py index fc621ef22b..e2fb72cb23 100644 --- a/tests/evm_backends/pyevm_env.py +++ b/tests/evm_backends/pyevm_env.py @@ -27,6 +27,7 @@ class PyEvmEnv(BaseEnv): """EVM backend environment using the Py-EVM library.""" INVALID_OPCODE_ERROR = "Invalid opcode" + OUT_OF_GAS_ERROR = "Out of gas" def __init__( self, diff --git a/tests/evm_backends/revm_env.py b/tests/evm_backends/revm_env.py index 5e4508f526..d7fb232e44 100644 --- a/tests/evm_backends/revm_env.py +++ b/tests/evm_backends/revm_env.py @@ -10,6 +10,7 @@ class RevmEnv(BaseEnv): INVALID_OPCODE_ERROR = "InvalidFEOpcode" + OUT_OF_GAS_ERROR = "OutOfGas" def __init__( self, diff --git a/tests/functional/builtins/codegen/test_abi_decode.py b/tests/functional/builtins/codegen/test_abi_decode.py index af8a2d6adb..a580ba12a0 100644 --- a/tests/functional/builtins/codegen/test_abi_decode.py +++ b/tests/functional/builtins/codegen/test_abi_decode.py @@ -1,6 +1,7 @@ import pytest from eth.codecs import abi +from tests.evm_backends.base_env import EvmError, ExecutionReverted from tests.utils import decimal_to_int from vyper.exceptions import ArgumentException, StructureException @@ -421,15 +422,17 @@ def abi_decode(x: Bytes[160]) -> uint256: @pytest.mark.parametrize( - "output_typ1,output_typ2,input_", + "output_typ1,output_typ2,input_,error,error_property", [ - ("DynArray[uint256, 3]", "uint256", b""), - ("DynArray[uint256, 3]", "uint256", b"\x01" * 128), - ("Bytes[5]", "address", b""), - ("Bytes[5]", "address", b"\x01" * 128), + ("DynArray[uint256, 3]", "uint256", b"", ExecutionReverted, ""), + ("DynArray[uint256, 3]", "uint256", b"\x01" * 128, EvmError, "OUT_OF_GAS_ERROR"), + ("Bytes[5]", "address", b"", ExecutionReverted, ""), + ("Bytes[5]", "address", b"\x01" * 128, EvmError, "OUT_OF_GAS_ERROR"), ], ) -def test_clamper_dynamic_tuple(get_contract, tx_failed, output_typ1, output_typ2, input_): +def test_clamper_dynamic_tuple( + get_contract, tx_failed, output_typ1, output_typ2, input_, error, error_property, env +): contract = f""" @external def abi_decode(x: Bytes[224]) -> ({output_typ1}, {output_typ2}): @@ -439,7 +442,7 @@ def abi_decode(x: Bytes[224]) -> ({output_typ1}, {output_typ2}): return a, b """ c = get_contract(contract) - with tx_failed(): + with tx_failed(error, exc_text=getattr(env, error_property, None)): c.abi_decode(input_) diff --git a/tests/functional/codegen/calling_convention/test_default_function.py b/tests/functional/codegen/calling_convention/test_default_function.py index 2e22bc855d..4d54e31f91 100644 --- a/tests/functional/codegen/calling_convention/test_default_function.py +++ b/tests/functional/codegen/calling_convention/test_default_function.py @@ -13,8 +13,10 @@ def __init__(): assert c.x() == 123 assert env.get_balance(c.address) == 0 + value = to_wei(0.1, "ether") + env.set_balance(env.deployer, value) with tx_failed(): - env.message_call(c.address, value=to_wei(0.1, "ether"), data=b"") # call default function + env.message_call(c.address, value=value, data=b"") # call default function assert env.get_balance(c.address) == 0 @@ -70,6 +72,7 @@ def __default__(): log Sent(msg.sender) """ c = get_contract(code) + env.set_balance(env.deployer, 10**17) with tx_failed(): env.message_call(c.address, value=10**17, data=b"") # call default function diff --git a/tests/functional/codegen/features/decorators/test_payable.py b/tests/functional/codegen/features/decorators/test_payable.py index e5157f5050..5e63eab7a1 100644 --- a/tests/functional/codegen/features/decorators/test_payable.py +++ b/tests/functional/codegen/features/decorators/test_payable.py @@ -182,6 +182,7 @@ def baz() -> bool: @pytest.mark.parametrize("code", nonpayable_code) def test_nonpayable_runtime_assertion(env, keccak, tx_failed, get_contract, code): c = get_contract(code) + env.set_balance(env.deployer, 10**18) c.foo(value=0) sig = keccak("foo()".encode()).hex()[:10] @@ -371,6 +372,7 @@ def __default__(): c = get_contract(code) env.message_call(c.address, value=0, data="0x12345678") + env.set_balance(env.deployer, 100) with tx_failed(): env.message_call(c.address, value=100, data="0x12345678") @@ -391,5 +393,6 @@ def __default__(): data = bytes([1, 2, 3, 4]) for i in range(5): calldata = "0x" + data[:i].hex() + env.set_balance(env.deployer, 100) with tx_failed(): env.message_call(c.address, value=100, data=calldata) diff --git a/tests/functional/codegen/features/test_assert_unreachable.py b/tests/functional/codegen/features/test_assert_unreachable.py index ee6ccef8da..16ac614cb9 100644 --- a/tests/functional/codegen/features/test_assert_unreachable.py +++ b/tests/functional/codegen/features/test_assert_unreachable.py @@ -1,3 +1,6 @@ +from tests.evm_backends.base_env import EvmError + + def test_unreachable_refund(env, get_contract, tx_failed): code = """ @external @@ -8,7 +11,7 @@ def foo(): c = get_contract(code) gas_sent = 10**6 - with tx_failed(): + with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): c.foo(gas=gas_sent, gas_price=10) assert env.last_result.gas_used == gas_sent # Drains all gas sent per INVALID opcode @@ -27,11 +30,11 @@ def foo(val: int128) -> bool: assert c.foo(2) is True - with tx_failed(exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): c.foo(1) - with tx_failed(exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): c.foo(-1) - with tx_failed(exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): c.foo(-2) @@ -53,9 +56,9 @@ def foo(val: int128) -> int128: assert c.foo(33) == -123 - with tx_failed(exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): c.foo(1) - with tx_failed(exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): c.foo(-1) @@ -68,5 +71,5 @@ def foo(): c = get_contract(code) - with tx_failed(exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): c.foo() diff --git a/tests/functional/examples/market_maker/test_on_chain_market_maker.py b/tests/functional/examples/market_maker/test_on_chain_market_maker.py index 4f76c93ecc..071afce5d6 100644 --- a/tests/functional/examples/market_maker/test_on_chain_market_maker.py +++ b/tests/functional/examples/market_maker/test_on_chain_market_maker.py @@ -37,7 +37,7 @@ def test_initial_state(market_maker): def test_initiate(env, market_maker, erc20, tx_failed): a0 = env.accounts[0] ether, ethers = to_wei(1, "ether"), to_wei(2, "ether") - env.set_balance(a0, ethers) + env.set_balance(a0, ethers * 2) erc20.approve(market_maker.address, ethers) market_maker.initiate(erc20.address, ether, value=ethers) assert market_maker.totalEthQty() == ethers diff --git a/tests/functional/examples/safe_remote_purchase/test_safe_remote_purchase.py b/tests/functional/examples/safe_remote_purchase/test_safe_remote_purchase.py index 4f891fb4d3..bb89375530 100644 --- a/tests/functional/examples/safe_remote_purchase/test_safe_remote_purchase.py +++ b/tests/functional/examples/safe_remote_purchase/test_safe_remote_purchase.py @@ -33,10 +33,10 @@ def get_balance(): def test_initial_state(env, tx_failed, get_contract, get_balance, contract_code): + env.set_balance(env.deployer, to_wei(2, "ether")) # Initial deposit has to be divisible by two with tx_failed(): get_contract(contract_code, value=13) - env.set_balance(env.deployer, to_wei(2, "ether")) # Seller puts item up for sale a0_pre_bal, a1_pre_bal = get_balance() c = get_contract(contract_code, value=to_wei(2, "ether")) @@ -73,8 +73,8 @@ def test_abort(env, tx_failed, get_balance, get_contract, contract_code): def test_purchase(env, get_contract, tx_failed, get_balance, contract_code): a0, a1, a2, a3 = env.accounts[:4] - env.set_balance(a0, 10**18) - env.set_balance(a1, 10**18) + for a in env.accounts[:4]: + env.set_balance(a, 10**18) init_bal_a0, init_bal_a1 = get_balance() c = get_contract(contract_code, value=2) From f5841c5981d88920214f67e56789675612071b0b Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 6 May 2024 16:15:30 +0200 Subject: [PATCH 2/2] Error messages as property --- tests/evm_backends/base_env.py | 12 ++++++++++-- tests/evm_backends/pyevm_env.py | 4 ++-- tests/evm_backends/revm_env.py | 4 ++-- .../codegen/features/test_assert_unreachable.py | 14 +++++++------- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/tests/evm_backends/base_env.py b/tests/evm_backends/base_env.py index 0e34d78d92..a8ab4d2367 100644 --- a/tests/evm_backends/base_env.py +++ b/tests/evm_backends/base_env.py @@ -44,8 +44,6 @@ class BaseEnv: It provides a common interface for deploying contracts and interacting with them. """ - INVALID_OPCODE_ERROR = "NotImplemented" # must be implemented by subclasses - OUT_OF_GAS_ERROR = "NotImplemented" # must be implemented by subclasses DEFAULT_CHAIN_ID = 1 def __init__(self, gas_limit: int, account_keys: list[PrivateKey]) -> None: @@ -197,6 +195,16 @@ def _parse_revert(output_bytes: bytes, error: Exception, gas_used: int): raise ExecutionReverted(f"0x{output_bytes.hex()}", gas_used) from error + @property + def invalid_opcode_error(self) -> str: + """Expected error message when invalid opcode is executed.""" + raise NotImplementedError # must be implemented by subclasses + + @property + def out_of_gas_error(self) -> str: + """Expected error message when user runs out of gas""" + raise NotImplementedError # must be implemented by subclasses + def _compile( source_code: str, diff --git a/tests/evm_backends/pyevm_env.py b/tests/evm_backends/pyevm_env.py index e2fb72cb23..6638308ff9 100644 --- a/tests/evm_backends/pyevm_env.py +++ b/tests/evm_backends/pyevm_env.py @@ -26,8 +26,8 @@ class PyEvmEnv(BaseEnv): """EVM backend environment using the Py-EVM library.""" - INVALID_OPCODE_ERROR = "Invalid opcode" - OUT_OF_GAS_ERROR = "Out of gas" + invalid_opcode_error = "Invalid opcode" + out_of_gas_error = "Out of gas" def __init__( self, diff --git a/tests/evm_backends/revm_env.py b/tests/evm_backends/revm_env.py index d7fb232e44..c23a74e158 100644 --- a/tests/evm_backends/revm_env.py +++ b/tests/evm_backends/revm_env.py @@ -9,8 +9,8 @@ class RevmEnv(BaseEnv): - INVALID_OPCODE_ERROR = "InvalidFEOpcode" - OUT_OF_GAS_ERROR = "OutOfGas" + invalid_opcode_error = "InvalidFEOpcode" + out_of_gas_error = "OutOfGas" def __init__( self, diff --git a/tests/functional/codegen/features/test_assert_unreachable.py b/tests/functional/codegen/features/test_assert_unreachable.py index 16ac614cb9..4c6aed5735 100644 --- a/tests/functional/codegen/features/test_assert_unreachable.py +++ b/tests/functional/codegen/features/test_assert_unreachable.py @@ -11,7 +11,7 @@ def foo(): c = get_contract(code) gas_sent = 10**6 - with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.invalid_opcode_error): c.foo(gas=gas_sent, gas_price=10) assert env.last_result.gas_used == gas_sent # Drains all gas sent per INVALID opcode @@ -30,11 +30,11 @@ def foo(val: int128) -> bool: assert c.foo(2) is True - with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.invalid_opcode_error): c.foo(1) - with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.invalid_opcode_error): c.foo(-1) - with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.invalid_opcode_error): c.foo(-2) @@ -56,9 +56,9 @@ def foo(val: int128) -> int128: assert c.foo(33) == -123 - with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.invalid_opcode_error): c.foo(1) - with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.invalid_opcode_error): c.foo(-1) @@ -71,5 +71,5 @@ def foo(): c = get_contract(code) - with tx_failed(EvmError, exc_text=env.INVALID_OPCODE_ERROR): + with tx_failed(EvmError, exc_text=env.invalid_opcode_error): c.foo()