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

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

Open
tserg opened this issue Jul 17, 2023 · 10 comments · May be fixed by #3546
Open

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

tserg opened this issue Jul 17, 2023 · 10 comments · May be fixed by #3546
Milestone

Comments

@tserg
Copy link
Collaborator

tserg commented Jul 17, 2023

Version Information

  • vyper Version (output of vyper --version): 9e3b9a2
  • 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.

@trocher
Copy link
Contributor

trocher commented Aug 2, 2023

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:

n = Expr.parse_value_expr(arg0, self.context)

interface A:
    def foo()-> uint256: nonpayable

@external
def bar(x:address):
    a: A = A(x)
    for i in range(a.foo(), bound = 12):
        pass

@fubuloubu
Copy link
Member

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:

n = Expr.parse_value_expr(arg0, self.context)

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

@charles-cooper
Copy link
Member

@trocher thanks for finding, will fix before release

@charles-cooper charles-cooper added this to the v0.3.10 milestone Aug 2, 2023
@trocher
Copy link
Contributor

trocher commented Aug 4, 2023

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.

@charles-cooper
Copy link
Member

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:

n = Expr.parse_value_expr(arg0, self.context)

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.

@trocher
Copy link
Contributor

trocher commented Aug 7, 2023

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)

def is_constant(self):
return self.constancy is Constancy.Constant or self.in_assertion or self.in_range_expr

@charles-cooper
Copy link
Member

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)

def is_constant(self):
return self.constancy is Constancy.Constant or self.in_assertion or self.in_range_expr

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

@trocher
Copy link
Contributor

trocher commented Aug 15, 2023

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)

@charles-cooper
Copy link
Member

charles-cooper commented Dec 30, 2024

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:

n = Expr.parse_value_expr(arg0, self.context)

interface A:
    def foo()-> uint256: nonpayable

@external
def bar(x:address):
    a: A = A(x)
    for i in range(a.foo(), bound = 12):
        pass

as of 194d60a, this triggers a compiler panic (slightly modified code for 0.4.x):

interface A:
    def foo()-> uint256: nonpayable

@external
def bar(x:address):
    a: A = A(x)
    for i: uint256 in range(extcall a.foo(), bound = 12):
        pass

the panic looks like

vyper.exceptions.CodegenPanic: unhandled exception typechecker missed this, parse_ExtCall

  contract "repros/3504.vy:7", function "bar", line 7:28 
       6     a: A = A(x)
  ---> 7     for i: uint256 in range(extcall a.foo(), bound = 12):
  -----------------------------------^
       8         pass

@charles-cooper charles-cooper modified the milestones: v0.3.10, v0.4.2 Dec 30, 2024
@charles-cooper
Copy link
Member

(the panic looks like a duplicate of / related to #3867 actually)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants