Skip to content

Commit

Permalink
Merge branch 'master' into test/export
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielSchiavini authored May 27, 2024
2 parents 58479b3 + ad9c10b commit 12ce736
Show file tree
Hide file tree
Showing 13 changed files with 200 additions and 70 deletions.
5 changes: 5 additions & 0 deletions docs/interfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ This imports the defined interface from the vyper file at ``an_interface.vy`` (o

Interfaces that implement functions with return values that require an upper bound (e.g. ``Bytes``, ``DynArray``, or ``String``), the upper bound defined in the interface represents the lower bound of the implementation. Assuming a function ``my_func`` returns a value ``String[1]`` in the interface, this would mean for the implementation function of ``my_func`` that the return value must have **at least** length 1. This behavior might change in the future.

.. note::

Prior to v0.4.0, ``implements`` required that events defined in an interface were re-defined in the "implementing" contract. As of v0.4.0, this is no longer required because events can be used just by importing them. Any events used in a contract will automatically be exported in the ABI output.


Extracting Interfaces
=====================

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2527,3 +2527,58 @@ def foo(a: DynArray[{typ}, 3], b: String[5]):
# Dynamic size is at least minimum (132 bytes * 2 + 2 (for 0x) = 266)
valid = data[:266]
env.message_call(c1.address, data=valid)


def test_make_setter_external_call(get_contract):
# variant of GH #3503
code = """
interface A:
def boo() -> uint256:nonpayable
a: DynArray[uint256, 10]
@external
def foo() -> DynArray[uint256, 10]:
self.a = [1, 2, extcall A(self).boo(), 4]
return self.a # returns [11, 12, 3, 4]
@external
def boo() -> uint256:
self.a = [11, 12, 13, 14, 15, 16]
self.a = []
# it should now be impossible to read any of [11, 12, 13, 14, 15, 16]
return 3
"""
c = get_contract(code)

assert c.foo() == [1, 2, 3, 4]


def test_make_setter_external_call2(get_contract):
# variant of GH #3503
code = """
interface A:
def boo(): nonpayable
a: DynArray[uint256, 10]
@external
def foo() -> DynArray[uint256, 10]:
self.a = [1, 2, self.baz(), 4]
return self.a # returns [11, 12, 3, 4]
@internal
def baz() -> uint256:
extcall A(self).boo()
return 3
@external
def boo():
self.a = [11, 12, 13, 14, 15, 16]
self.a = []
# it should now be impossible to read any of [11, 12, 13, 14, 15, 16]
"""
c = get_contract(code)

assert c.foo() == [1, 2, 3, 4]
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

def test_selfcall_code(get_contract):
selfcall_code = """
@internal
def _foo() -> int128:
return 3
Expand All @@ -28,15 +27,13 @@ def bar() -> int128:

def test_selfcall_code_2(get_contract, keccak):
selfcall_code_2 = """
@internal
def _double(x: int128) -> int128:
return x * 2
@external
def returnten() -> int128:
return self._double(5)
@internal
def _hashy(x: bytes32) -> bytes32:
return keccak256(x)
Expand Down Expand Up @@ -624,6 +621,49 @@ def bar() -> Bytes[6]:
assert c.bar() == b"hello"


def test_make_setter_internal_call(get_contract):
# cf. GH #3503
code = """
a:DynArray[uint256,2]
@external
def foo() -> DynArray[uint256,2]:
# Initial value
self.a = [1, 2]
self.a = [self.bar(1), self.bar(0)]
return self.a
@internal
def bar(i: uint256) -> uint256:
return self.a[i]
"""
c = get_contract(code)

assert c.foo() == [2, 1]


def test_make_setter_internal_call2(get_contract):
# cf. GH #3503
code = """
a: DynArray[uint256, 10]
@external
def foo() -> DynArray[uint256, 10]:
self.a = [1, 2, self.boo(), 4]
return self.a # returns [11, 12, 3, 4]
@internal
def boo() -> uint256:
self.a = [11, 12, 13, 14, 15, 16]
self.a = []
# it should now be impossible to read any of [11, 12, 13, 14, 15, 16]
return 3
"""
c = get_contract(code)

assert c.foo() == [1, 2, 3, 4]


def test_dynamically_sized_struct_member_as_arg_2(get_contract):
contract = """
struct X:
Expand Down
9 changes: 0 additions & 9 deletions tests/functional/codegen/features/decorators/test_public.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,3 @@ def foo():
"""

assert_compile_failed(lambda: get_contract(code), FunctionDeclarationException)


def test_invalid_if_visibility_isnt_declared(assert_compile_failed, get_contract):
code = """
def foo():
x: uint256 = 1
"""

assert_compile_failed(lambda: get_contract(code), FunctionDeclarationException)
34 changes: 34 additions & 0 deletions tests/functional/codegen/features/iteration/test_for_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,37 @@ def foo() -> DynArray[int256, 10]:
return
with pytest.raises(StaticAssertionException):
get_contract(code)


def test_for_range_start_double_eval(get_contract, tx_failed):
code = """
@external
def foo() -> (uint256, DynArray[uint256, 3]):
x:DynArray[uint256, 3] = [3, 1]
res: DynArray[uint256, 3] = empty(DynArray[uint256, 3])
for i:uint256 in range(x.pop(),x.pop(), bound = 3):
res.append(i)
return len(x), res
"""
c = get_contract(code)
length, res = c.foo()

assert (length, res) == (0, [1, 2])


def test_for_range_stop_double_eval(get_contract, tx_failed):
code = """
@external
def foo() -> (uint256, DynArray[uint256, 3]):
x:DynArray[uint256, 3] = [3, 3]
res: DynArray[uint256, 3] = empty(DynArray[uint256, 3])
for i:uint256 in range(x.pop(), bound = 3):
res.append(i)
return len(x), res
"""
c = get_contract(code)
length, res = c.foo()

assert (length, res) == (1, [0, 1, 2])
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,6 @@ def foo() -> int128:
pass
""",
"""
def foo() -> int128:
q: int128 = 111
return q
""",
"""
q: int128
def foo() -> int128:
return self.q
""",
"""
@external
def test_func() -> int128:
return (1, 2)
Expand Down
1 change: 1 addition & 0 deletions vyper/codegen/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,7 @@ def parse_Call(self):

assert isinstance(func_t, ContractFunctionT)
assert func_t.is_internal or func_t.is_constructor

return self_call.ir_for_self_call(self.expr, self.context)

@classmethod
Expand Down
5 changes: 5 additions & 0 deletions vyper/codegen/function_definitions/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class _FuncIRInfo:
func_t: ContractFunctionT
gas_estimate: Optional[int] = None
frame_info: Optional[FrameInfo] = None
func_ir: Optional["InternalFuncIR"] = None

@property
def visibility(self):
Expand Down Expand Up @@ -56,6 +57,10 @@ def set_frame_info(self, frame_info: FrameInfo) -> None:
else:
self.frame_info = frame_info

def set_func_ir(self, func_ir: "InternalFuncIR") -> None:
assert self.func_t.is_internal or self.func_t.is_deploy
self.func_ir = func_ir

@property
# common entry point for external function with kwargs
def external_function_base_entry_label(self) -> str:
Expand Down
5 changes: 4 additions & 1 deletion vyper/codegen/function_definitions/internal_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,7 @@ def generate_ir_for_internal_function(
func_t._ir_info.gas_estimate = ir_node.gas
tag_frame_info(func_t, context)

return InternalFuncIR(ir_node)
ret = InternalFuncIR(ir_node)
func_t._ir_info.func_ir = ret

return ret
18 changes: 16 additions & 2 deletions vyper/codegen/ir_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,25 @@ def cache_when_complex(self, name):

@cached_property
def referenced_variables(self):
ret = set()
ret = getattr(self, "_referenced_variables", set())

for arg in self.args:
ret |= arg.referenced_variables

ret |= getattr(self, "_referenced_variables", set())
if getattr(self, "is_self_call", False):
ret |= self.invoked_function_ir.func_ir.referenced_variables

return ret

@cached_property
def contains_risky_call(self):
ret = self.value in ("call", "delegatecall", "create", "create2")

for arg in self.args:
ret |= arg.contains_risky_call

if getattr(self, "is_self_call", False):
ret |= self.invoked_function_ir.func_ir.contains_risky_call

return ret

Expand Down
1 change: 1 addition & 0 deletions vyper/codegen/self_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,5 @@ def ir_for_self_call(stmt_expr, context):
add_gas_estimate=func_t._ir_info.gas_estimate,
)
o.is_self_call = True
o.invoked_function_ir = func_t._ir_info.func_ir
return o
65 changes: 33 additions & 32 deletions vyper/codegen/stmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ def parse_Assign(self):

ret = ["seq"]
overlap = len(dst.referenced_variables & src.referenced_variables) > 0
overlap |= len(dst.referenced_variables) > 0 and src.contains_risky_call
overlap |= dst.contains_risky_call and len(src.referenced_variables) > 0
if overlap and not dst.typ._is_prim_word:
# there is overlap between the lhs and rhs, and the type is
# complex - i.e., it spans multiple words. for safety, we
Expand Down Expand Up @@ -200,44 +202,43 @@ def _parse_For_range(self):
# sanity check that the following `end - start` is a valid operation
assert start.typ == end.typ == target_type

if "bound" in kwargs:
with end.cache_when_complex("end") as (b1, end):
# note: the check for rounds<=rounds_bound happens in asm
# generation for `repeat`.
clamped_start = clamp_le(start, end, target_type.is_signed)
rounds = b1.resolve(IRnode.from_list(["sub", end, clamped_start]))
rounds_bound = kwargs.pop("bound").int_value()
else:
rounds = end.int_value() - start.int_value()
rounds_bound = rounds
with start.cache_when_complex("start") as (b1, start):
if "bound" in kwargs:
with end.cache_when_complex("end") as (b2, end):
# note: the check for rounds<=rounds_bound happens in asm
# generation for `repeat`.
clamped_start = clamp_le(start, end, target_type.is_signed)
rounds = b2.resolve(IRnode.from_list(["sub", end, clamped_start]))
rounds_bound = kwargs.pop("bound").int_value()
else:
rounds = end.int_value() - start.int_value()
rounds_bound = rounds

assert len(kwargs) == 0 # sanity check stray keywords
assert len(kwargs) == 0 # sanity check stray keywords

if rounds_bound < 1: # pragma: nocover
raise TypeCheckFailure("unreachable: unchecked 0 bound")
if rounds_bound < 1: # pragma: nocover
raise TypeCheckFailure("unreachable: unchecked 0 bound")

varname = self.stmt.target.target.id
i = IRnode.from_list(self.context.fresh_varname("range_ix"), typ=target_type)
iptr = self.context.new_variable(varname, target_type)
varname = self.stmt.target.target.id
i = IRnode.from_list(self.context.fresh_varname("range_ix"), typ=target_type)
iptr = self.context.new_variable(varname, target_type)

self.context.forvars[varname] = True
self.context.forvars[varname] = True

loop_body = ["seq"]
# store the current value of i so it is accessible to userland
loop_body.append(["mstore", iptr, i])
loop_body.append(parse_body(self.stmt.body, self.context))

# NOTE: codegen for `repeat` inserts an assertion that
# (gt rounds_bound rounds). note this also covers the case where
# rounds < 0.
# if we ever want to remove that, we need to manually add the assertion
# where it makes sense.
ir_node = IRnode.from_list(
["repeat", i, start, rounds, rounds_bound, loop_body], error_msg="range() bounds check"
)
del self.context.forvars[varname]
loop_body = ["seq"]
# store the current value of i so it is accessible to userland
loop_body.append(["mstore", iptr, i])
loop_body.append(parse_body(self.stmt.body, self.context))

del self.context.forvars[varname]

return ir_node
# NOTE: codegen for `repeat` inserts an assertion that
# (gt rounds_bound rounds). note this also covers the case where
# rounds < 0.
# if we ever want to remove that, we need to manually add the assertion
# where it makes sense.
loop = ["repeat", i, start, rounds, rounds_bound, loop_body]
return b1.resolve(IRnode.from_list(loop, error_msg="range() bounds check"))

def _parse_For_list(self):
with self.context.range_scope():
Expand Down
Loading

0 comments on commit 12ce736

Please sign in to comment.