Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for modules with variables #3707

Closed
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a59d575
wip - module variables
charles-cooper Dec 20, 2023
e353a15
add size_in_bytes to module
charles-cooper Dec 20, 2023
33dffaf
wip - storage allocator
charles-cooper Dec 15, 2023
654256b
remove ImportedVariable thing
charles-cooper Dec 20, 2023
bcc03d6
wip - add get_element_ptr for module, fix some logic in Expr.parse_At…
charles-cooper Dec 20, 2023
e9b867a
call set_data_positions recursively
charles-cooper Dec 21, 2023
037d5a6
add a sanity check
charles-cooper Dec 21, 2023
3512e3f
rename some size calculators and add immutable_bytes_required to Vype…
charles-cooper Dec 21, 2023
86c299a
add a comment
charles-cooper Dec 21, 2023
9e689b9
add Context.self_ptr helper
charles-cooper Dec 21, 2023
8becda2
add a note
charles-cooper Dec 21, 2023
a0d0bd1
Merge branch 'master' into feat/module_variables
charles-cooper Dec 21, 2023
9ac072b
improve Context.self_ptr
charles-cooper Dec 21, 2023
4e102bf
add function variable read/writes analysis
charles-cooper Dec 21, 2023
9f100d9
calculate pointer things
charles-cooper Dec 22, 2023
2d05699
quash mypy
charles-cooper Dec 22, 2023
1585bdc
wip - handle immutables
charles-cooper Dec 22, 2023
bf6e99c
feat: replace `enum` with `flag` keyword (#3697)
AlbertoCentonze Dec 23, 2023
1824321
refactor: make `assert_tx_failed` a contextmanager (#3706)
DanielSchiavini Dec 23, 2023
7489e34
feat: allow `range(x, y, bound=N)` (#3679)
DanielSchiavini Dec 24, 2023
1040f3e
feat: improve panics in IR generation (#3708)
charles-cooper Dec 25, 2023
977851a
add special visibility for the __init__ function
charles-cooper Dec 27, 2023
8af611b
remove unused MemoryOffset, CalldataOffset classes
charles-cooper Dec 28, 2023
c241e91
wip - allow init functions to be called from init func
charles-cooper Dec 29, 2023
3de7bb2
rename DataLocations.CODE to IMMUTABLES
charles-cooper Dec 29, 2023
ee91a52
refactor set_data_positions and rework VarInfo positions API
charles-cooper Dec 29, 2023
5e08300
thread new offset through codegen
charles-cooper Dec 29, 2023
1e393fa
mark storage layout override tests as xfail
charles-cooper Dec 29, 2023
6cf9ff0
Merge branch 'master' into feat/module_variables
charles-cooper Dec 30, 2023
7a82180
Merge branch 'master' into feat/module_variables
charles-cooper Jan 2, 2024
eff76e0
Merge branch 'master' into feat/module_variables
charles-cooper Jan 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/control-structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,11 @@ Another use of range can be with ``START`` and ``STOP`` bounds.
Here, ``START`` and ``STOP`` are literal integers, with ``STOP`` being a greater value than ``START``. ``i`` begins as ``START`` and increments by one until it is equal to ``STOP``.

Finally, it is possible to use ``range`` with runtime `start` and `stop` values as long as a constant `bound` value is provided.
In this case, Vyper checks at runtime that `end - start <= bound`.
``N`` must be a compile-time constant.

.. code-block:: python
for i in range(a, a + N):
for i in range(start, end, bound=N):
...
``a`` is a variable with an integer type and ``N`` is a literal integer greater than zero. ``i`` begins as ``a`` and increments by one until it is equal to ``a + N``. If ``a + N`` would overflow, execution will revert.
4 changes: 2 additions & 2 deletions docs/testing-contracts-ethtester.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ To test events and failed transactions we expand our simple storage contract to

Next, we take a look at the two fixtures that will allow us to read the event logs and to check for failed transactions.

.. literalinclude:: ../tests/base_conftest.py
.. literalinclude:: ../tests/conftest.py
:language: python
:pyobject: assert_tx_failed
:pyobject: tx_failed

The fixture to assert failed transactions defaults to check for a ``TransactionFailed`` exception, but can be used to check for different exceptions too, as shown below. Also note that the chain gets reverted to the state before the failed transaction.

Expand Down
22 changes: 11 additions & 11 deletions docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -376,22 +376,22 @@ On the ABI level the Fixed-size bytes array is annotated as ``string``.

example_str: String[100] = "Test String"

Enums
Flags
-----

**Keyword:** ``enum``
**Keyword:** ``flag``

Enums are custom defined types. An enum must have at least one member, and can hold up to a maximum of 256 members.
Flags are custom defined types. A flag must have at least one member, and can hold up to a maximum of 256 members.
The members are represented by ``uint256`` values in the form of 2\ :sup:`n` where ``n`` is the index of the member in the range ``0 <= n <= 255``.

.. code-block:: python

# Defining an enum with two members
enum Roles:
# Defining a flag with two members
flag Roles:
ADMIN
USER

# Declaring an enum variable
# Declaring a flag variable
role: Roles = Roles.ADMIN

# Returning a member
Expand Down Expand Up @@ -426,13 +426,13 @@ Operator Description
``~x`` Bitwise not
============= ======================

Enum members can be combined using the above bitwise operators. While enum members have values that are power of two, enum member combinations may not.
Flag members can be combined using the above bitwise operators. While flag members have values that are power of two, flag member combinations may not.

The ``in`` and ``not in`` operators can be used in conjunction with enum member combinations to check for membership.
The ``in`` and ``not in`` operators can be used in conjunction with flag member combinations to check for membership.

.. code-block:: python

enum Roles:
flag Roles:
MANAGER
ADMIN
USER
Expand All @@ -447,7 +447,7 @@ The ``in`` and ``not in`` operators can be used in conjunction with enum member
def bar(a: Roles) -> bool:
return a not in (Roles.MANAGER | Roles.USER)

Note that ``in`` is not the same as strict equality (``==``). ``in`` checks that *any* of the flags on two enum objects are simultaneously set, while ``==`` checks that two enum objects are bit-for-bit equal.
Note that ``in`` is not the same as strict equality (``==``). ``in`` checks that *any* of the flags on two flag objects are simultaneously set, while ``==`` checks that two flag objects are bit-for-bit equal.

The following code uses bitwise operations to add and revoke permissions from a given ``Roles`` object.

Expand Down Expand Up @@ -488,7 +488,7 @@ Fixed-size Lists

Fixed-size lists hold a finite number of elements which belong to a specified type.

Lists can be declared with ``_name: _ValueType[_Integer]``, except ``Bytes[N]``, ``String[N]`` and enums.
Lists can be declared with ``_name: _ValueType[_Integer]``, except ``Bytes[N]``, ``String[N]`` and flags.

.. code-block:: python

Expand Down
28 changes: 6 additions & 22 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import logging
from contextlib import contextmanager
from functools import wraps

import hypothesis
Expand Down Expand Up @@ -411,23 +412,6 @@ def assert_compile_failed(function_to_test, exception=Exception):
return assert_compile_failed


# TODO this should not be a fixture
@pytest.fixture
def search_for_sublist():
def search_for_sublist(ir, sublist):
_list = ir.to_list() if hasattr(ir, "to_list") else ir
if _list == sublist:
return True
if isinstance(_list, list):
for i in _list:
ret = search_for_sublist(i, sublist)
if ret is True:
return ret
return False

return search_for_sublist


@pytest.fixture
def create2_address_of(keccak):
def _f(_addr, _salt, _initcode):
Expand Down Expand Up @@ -484,16 +468,16 @@ def get_logs(tx_hash, c, event_name):
return get_logs


# TODO replace me with function like `with anchor_state()`
@pytest.fixture(scope="module")
def assert_tx_failed(tester):
def assert_tx_failed(function_to_test, exception=TransactionFailed, exc_text=None):
def tx_failed(tester):
@contextmanager
def fn(exception=TransactionFailed, exc_text=None):
snapshot_id = tester.take_snapshot()
with pytest.raises(exception) as excinfo:
function_to_test()
yield excinfo
tester.revert_to_snapshot(snapshot_id)
if exc_text:
# TODO test equality
assert exc_text in str(excinfo.value), (exc_text, excinfo.value)

return assert_tx_failed
return fn
25 changes: 15 additions & 10 deletions tests/functional/builtins/codegen/test_abi_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def abi_decode(x: Bytes[32]) -> uint256:
b"\x01" * 96, # Length of byte array is beyond size bound of output type
],
)
def test_clamper(get_contract, assert_tx_failed, input_):
def test_clamper(get_contract, tx_failed, input_):
contract = """
@external
def abi_decode(x: Bytes[96]) -> (uint256, uint256):
Expand All @@ -341,10 +341,11 @@ def abi_decode(x: Bytes[96]) -> (uint256, uint256):
return a, b
"""
c = get_contract(contract)
assert_tx_failed(lambda: c.abi_decode(input_))
with tx_failed():
c.abi_decode(input_)


def test_clamper_nested_uint8(get_contract, assert_tx_failed):
def test_clamper_nested_uint8(get_contract, tx_failed):
# check that _abi_decode clamps on word-types even when it is in a nested expression
# decode -> validate uint8 -> revert if input >= 256 -> cast back to uint256
contract = """
Expand All @@ -355,10 +356,11 @@ def abi_decode(x: uint256) -> uint256:
"""
c = get_contract(contract)
assert c.abi_decode(255) == 255
assert_tx_failed(lambda: c.abi_decode(256))
with tx_failed():
c.abi_decode(256)


def test_clamper_nested_bytes(get_contract, assert_tx_failed):
def test_clamper_nested_bytes(get_contract, tx_failed):
# check that _abi_decode clamps dynamic even when it is in a nested expression
# decode -> validate Bytes[20] -> revert if len(input) > 20 -> convert back to -> add 1
contract = """
Expand All @@ -369,7 +371,8 @@ def abi_decode(x: Bytes[96]) -> Bytes[21]:
"""
c = get_contract(contract)
assert c.abi_decode(abi.encode("(bytes)", (b"bc",))) == b"abc"
assert_tx_failed(lambda: c.abi_decode(abi.encode("(bytes)", (b"a" * 22,))))
with tx_failed():
c.abi_decode(abi.encode("(bytes)", (b"a" * 22,)))


@pytest.mark.parametrize(
Expand All @@ -381,7 +384,7 @@ def abi_decode(x: Bytes[96]) -> Bytes[21]:
("Bytes[5]", b"\x01" * 192),
],
)
def test_clamper_dynamic(get_contract, assert_tx_failed, output_typ, input_):
def test_clamper_dynamic(get_contract, tx_failed, output_typ, input_):
contract = f"""
@external
def abi_decode(x: Bytes[192]) -> {output_typ}:
Expand All @@ -390,7 +393,8 @@ def abi_decode(x: Bytes[192]) -> {output_typ}:
return a
"""
c = get_contract(contract)
assert_tx_failed(lambda: c.abi_decode(input_))
with tx_failed():
c.abi_decode(input_)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -422,7 +426,7 @@ def abi_decode(x: Bytes[160]) -> uint256:
("Bytes[5]", "address", b"\x01" * 128),
],
)
def test_clamper_dynamic_tuple(get_contract, assert_tx_failed, output_typ1, output_typ2, input_):
def test_clamper_dynamic_tuple(get_contract, tx_failed, output_typ1, output_typ2, input_):
contract = f"""
@external
def abi_decode(x: Bytes[224]) -> ({output_typ1}, {output_typ2}):
Expand All @@ -432,7 +436,8 @@ def abi_decode(x: Bytes[224]) -> ({output_typ1}, {output_typ2}):
return a, b
"""
c = get_contract(contract)
assert_tx_failed(lambda: c.abi_decode(input_))
with tx_failed():
c.abi_decode(input_)


FAIL_LIST = [
Expand Down
5 changes: 3 additions & 2 deletions tests/functional/builtins/codegen/test_addmod.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
def test_uint256_addmod(assert_tx_failed, get_contract_with_gas_estimation):
def test_uint256_addmod(tx_failed, get_contract_with_gas_estimation):
uint256_code = """
@external
def _uint256_addmod(x: uint256, y: uint256, z: uint256) -> uint256:
Expand All @@ -11,7 +11,8 @@ def _uint256_addmod(x: uint256, y: uint256, z: uint256) -> uint256:
assert c._uint256_addmod(32, 2, 32) == 2
assert c._uint256_addmod((2**256) - 1, 0, 2) == 1
assert c._uint256_addmod(2**255, 2**255, 6) == 4
assert_tx_failed(lambda: c._uint256_addmod(1, 2, 0))
with tx_failed():
c._uint256_addmod(1, 2, 0)


def test_uint256_addmod_ext_call(
Expand Down
16 changes: 9 additions & 7 deletions tests/functional/builtins/codegen/test_as_wei_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@


@pytest.mark.parametrize("denom,multiplier", wei_denoms.items())
def test_wei_uint256(get_contract, assert_tx_failed, denom, multiplier):
def test_wei_uint256(get_contract, tx_failed, denom, multiplier):
code = f"""
@external
def foo(a: uint256) -> uint256:
Expand All @@ -36,11 +36,12 @@ def foo(a: uint256) -> uint256:
assert c.foo(value) == value * (10**multiplier)

value = (2**256 - 1) // (10 ** (multiplier - 1))
assert_tx_failed(lambda: c.foo(value))
with tx_failed():
c.foo(value)


@pytest.mark.parametrize("denom,multiplier", wei_denoms.items())
def test_wei_int128(get_contract, assert_tx_failed, denom, multiplier):
def test_wei_int128(get_contract, tx_failed, denom, multiplier):
code = f"""
@external
def foo(a: int128) -> uint256:
Expand All @@ -54,7 +55,7 @@ def foo(a: int128) -> uint256:


@pytest.mark.parametrize("denom,multiplier", wei_denoms.items())
def test_wei_decimal(get_contract, assert_tx_failed, denom, multiplier):
def test_wei_decimal(get_contract, tx_failed, denom, multiplier):
code = f"""
@external
def foo(a: decimal) -> uint256:
Expand All @@ -69,20 +70,21 @@ def foo(a: decimal) -> uint256:

@pytest.mark.parametrize("value", (-1, -(2**127)))
@pytest.mark.parametrize("data_type", ["decimal", "int128"])
def test_negative_value_reverts(get_contract, assert_tx_failed, value, data_type):
def test_negative_value_reverts(get_contract, tx_failed, value, data_type):
code = f"""
@external
def foo(a: {data_type}) -> uint256:
return as_wei_value(a, "ether")
"""

c = get_contract(code)
assert_tx_failed(lambda: c.foo(value))
with tx_failed():
c.foo(value)


@pytest.mark.parametrize("denom,multiplier", wei_denoms.items())
@pytest.mark.parametrize("data_type", ["decimal", "int128", "uint256"])
def test_zero_value(get_contract, assert_tx_failed, denom, multiplier, data_type):
def test_zero_value(get_contract, tx_failed, denom, multiplier, data_type):
code = f"""
@external
def foo(a: {data_type}) -> uint256:
Expand Down
21 changes: 12 additions & 9 deletions tests/functional/builtins/codegen/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,10 +486,10 @@ def test_memory_variable_convert(x: {i_typ}) -> {o_typ}:

@pytest.mark.parametrize("typ", ["uint8", "int128", "int256", "uint256"])
@pytest.mark.parametrize("val", [1, 2, 2**128, 2**256 - 1, 2**256 - 2])
def test_enum_conversion(get_contract_with_gas_estimation, assert_compile_failed, val, typ):
def test_flag_conversion(get_contract_with_gas_estimation, assert_compile_failed, val, typ):
roles = "\n ".join([f"ROLE_{i}" for i in range(256)])
contract = f"""
enum Roles:
flag Roles:
{roles}

@external
Expand All @@ -510,11 +510,11 @@ def bar(a: uint256) -> Roles:

@pytest.mark.parametrize("typ", ["uint8", "int128", "int256", "uint256"])
@pytest.mark.parametrize("val", [1, 2, 3, 4, 2**128, 2**256 - 1, 2**256 - 2])
def test_enum_conversion_2(
get_contract_with_gas_estimation, assert_compile_failed, assert_tx_failed, val, typ
def test_flag_conversion_2(
get_contract_with_gas_estimation, assert_compile_failed, tx_failed, val, typ
):
contract = f"""
enum Status:
flag Status:
STARTED
PAUSED
STOPPED
Expand All @@ -529,7 +529,8 @@ def foo(a: {typ}) -> Status:
if lo <= val <= hi:
assert c.foo(val) == val
else:
assert_tx_failed(lambda: c.foo(val))
with tx_failed():
c.foo(val)
else:
assert_compile_failed(lambda: get_contract_with_gas_estimation(contract), TypeMismatch)

Expand Down Expand Up @@ -608,7 +609,7 @@ def foo() -> {t_bytes}:
@pytest.mark.parametrize("i_typ,o_typ,val", generate_reverting_cases())
@pytest.mark.fuzzing
def test_conversion_failures(
get_contract_with_gas_estimation, assert_compile_failed, assert_tx_failed, i_typ, o_typ, val
get_contract_with_gas_estimation, assert_compile_failed, tx_failed, i_typ, o_typ, val
):
"""
Test multiple contracts and check for a specific exception.
Expand Down Expand Up @@ -650,7 +651,8 @@ def foo():
"""

c2 = get_contract_with_gas_estimation(contract_2)
assert_tx_failed(lambda: c2.foo())
with tx_failed():
c2.foo()

contract_3 = f"""
@external
Expand All @@ -659,4 +661,5 @@ def foo(bar: {i_typ}) -> {o_typ}:
"""

c3 = get_contract_with_gas_estimation(contract_3)
assert_tx_failed(lambda: c3.foo(val))
with tx_failed():
c3.foo(val)
Loading
Loading