vyper icon indicating copy to clipboard operation
vyper copied to clipboard

bug: calls to state-modifying functions in `range` expressions are not caught

Open tserg opened this issue 1 year ago • 8 comments

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.

tserg avatar Jul 17 '23 14:07 tserg

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

trocher avatar Aug 02 '23 11:08 trocher

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

fubuloubu avatar Aug 02 '23 14:08 fubuloubu

@trocher thanks for finding, will fix before release

charles-cooper avatar Aug 02 '23 16:08 charles-cooper

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.

trocher avatar Aug 04 '23 12:08 trocher

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.

charles-cooper avatar Aug 07 '23 15:08 charles-cooper

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

trocher avatar Aug 07 '23 16:08 trocher

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

ah interesting -- do you think this can lead to any bugs?

charles-cooper avatar Aug 07 '23 17:08 charles-cooper

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 for x when the range is on the form range(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)

trocher avatar Aug 15 '23 11:08 trocher