vyper
vyper copied to clipboard
bug: calls to state-modifying functions in `range` expressions are not caught
Version Information
- vyper Version (output of
vyper --version
): https://github.com/vyperlang/vyper/commit/9e3b9a2b8ae55aa83b5450080f750be15f819de7 - OS: linux
- Python Version (output of
python --version
): 3.10.4
What's your issue about?
The type checker does not catch the use of a state-modifying function call in a range expression, this leads the code generator to fail due to an assertion: assert use_staticcall, "typechecker missed this"
interface A:
def foo()-> uint256:nonpayable
@external
def bar(x:address):
a: A = A(x)
for i in range(a.foo(), a.foo()+1):
pass
h/t @trocher
How can it be fixed?
Fill this in if you know how to fix it.
Note that with commit https://github.com/vyperlang/vyper/commit/d48438e10722db8fc9e74d8ed434745e3b0d31cf
The codegen no longer prevent the bug in certain case, leading contracts with side effect in range
to compiles successfully:
https://github.com/vyperlang/vyper/blob/cfda16c734ecddc170079817cd96b14e4fe24586/vyper/codegen/stmt.py#L268
interface A:
def foo()-> uint256: nonpayable
@external
def bar(x:address):
a: A = A(x)
for i in range(a.foo(), bound = 12):
pass
Note that with commit https://github.com/vyperlang/vyper/commit/d48438e10722db8fc9e74d8ed434745e3b0d31cf
The codegen no longer prevent the bug in certain case, leading contracts with side effect in
range
to compiles successfully:https://github.com/vyperlang/vyper/blob/cfda16c734ecddc170079817cd96b14e4fe24586/vyper/codegen/stmt.py#L268
interface A: def foo()-> uint256: nonpayable @external def bar(x:address): a: A = A(x) for i in range(a.foo(), bound = 12): pass
This may not be as big of a deal, since it is only called once
@trocher thanks for finding, will fix before release
Also this one would currently compile even if it is really an edge case:
@external
def bar(x:address):
for i in range(1 if raw_call(x, b'', revert_on_failure=False) else 2 , bound = 12):
pass
Spent some time compiling a list of state modifying expressions:
- state modifying external call
- state modifying internal call
-
raw_call
-
pop()
when used on a Dynamic Array stored in the storage -
create_minimal_proxy_to
-
create_copy_of
-
create_from_blueprint
For pop
we have that #3172 btw.
Note that with commit d48438e
The codegen no longer prevent the bug in certain case, leading contracts with side effect in
range
to compiles successfully:https://github.com/vyperlang/vyper/blob/cfda16c734ecddc170079817cd96b14e4fe24586/vyper/codegen/stmt.py#L268
interface A: def foo()-> uint256: nonpayable @external def bar(x:address): a: A = A(x) for i in range(a.foo(), bound = 12): pass
@trocher i think this is a feature, not a bug? there isn't really any ambiguity about side effect ordering or anything here.
Yes I agree, in this case there is no ambiguity or anything, my concern was rather about the semantics of range
as I understood that it must always be constant (from definition of is_constant
)
https://github.com/vyperlang/vyper/blob/728a27677240fdd55a4144d04b31004f8330847c/vyper/codegen/context.py#L96-L97
Yes I agree, in this case there is no ambiguity or anything, my concern was rather about the semantics of
range
as I understood that it must always be constant (from definition ofis_constant
)https://github.com/vyperlang/vyper/blob/728a27677240fdd55a4144d04b31004f8330847c/vyper/codegen/context.py#L96-L97
ah interesting -- do you think this can lead to any bugs?
I don't see how it could lead to an issue in this specific case, it is really only about the semantics of range
which changes and now becomes slightly more complex. Before we roughly had the following:
range
's arguments must be constant
And now something along those lines:
range
's arguments must be constant except forx
when the range is on the formrange(x,bound=N)
.
Note that I use here the term constant
in the sense of Constancy.Constant
and not Vyper's constants (a: constant(uint256)=1
)