Repository: vyperlang/vyper

Issue:
Compiler error when doing unsigned math for storage array index
### Version Information

* vyper Version (output of `vyper --version`): 0.2.5+commit.a0c561c
* OS: linux
* Python Version (output of `python --version`): 3.8.5
* Environment (output of `pip freeze`): brownie

### What's your issue about?
Error text:
```python
  File "vyper/cli/vyper_json.py", line 394, in compile_json
    compiler_data, warn_data = compile_from_input_dict(input_dict, exc_handler, root_path)
  File "vyper/cli/vyper_json.py", line 326, in compile_from_input_dict
    return exc_handler(contract_path, exc, "compiler"), {}
  File "vyper/cli/vyper_json.py", line 102, in exc_handler_raises
    raise exception
  File "vyper/cli/vyper_json.py", line 318, in compile_from_input_dict
    data = vyper.compile_codes(
  File "vyper/opcodes.py", line 223, in _wrapper
    return fn(*args, **kwargs)
  File "vyper/compiler/__init__.py", line 110, in compile_codes
    raise exc
  File "vyper/compiler/__init__.py", line 105, in compile_codes
    out[contract_name][output_format] = OUTPUT_FORMATS[output_format](compiler_data)
  File "vyper/compiler/output.py", line 54, in build_abi_output
    gas_estimates = build_gas_estimates(compiler_data.lll_nodes)
  File "vyper/compiler/phases.py", line 102, in lll_nodes
    self._gen_lll()
  File "vyper/compiler/phases.py", line 97, in _gen_lll
    self._lll_nodes, self._lll_runtime = generate_lll_nodes(self.source_code, self.global_ctx)
  File "vyper/compiler/phases.py", line 225, in generate_lll_nodes
    lll_nodes, lll_runtime = parser.parse_tree_to_lll(source_code, global_ctx)
  File "vyper/parser/parser.py", line 204, in parse_tree_to_lll
    o, runtime = parse_other_functions(
  File "vyper/parser/parser.py", line 112, in parse_other_functions
    parse_function(
  File "vyper/parser/function_definitions/parse_function.py", line 55, in parse_function
    o = parse_internal_function(code=code, sig=sig, context=context,)
  File "vyper/parser/function_definitions/parse_internal_function.py", line 252, in parse_internal_function
    + [parse_body(c, context) for c in code.body]
  File "vyper/parser/function_definitions/parse_internal_function.py", line 252, in <listcomp>
    + [parse_body(c, context) for c in code.body]
  File "vyper/parser/stmt.py", line 641, in parse_body
    return parse_stmt(code, context)
  File "vyper/parser/stmt.py", line 635, in parse_stmt
    return Stmt(stmt, context).lll_node
  File "vyper/parser/stmt.py", line 41, in __init__
    self.lll_node = fn()
  File "vyper/parser/stmt.py", line 267, in parse_For
    ["repeat", pos, start, rounds, parse_body(self.stmt.body, self.context)],
  File "vyper/parser/stmt.py", line 645, in parse_body
    lll = parse_stmt(stmt, context)
  File "vyper/parser/stmt.py", line 635, in parse_stmt
    return Stmt(stmt, context).lll_node
  File "vyper/parser/stmt.py", line 41, in __init__
    self.lll_node = fn()
  File "vyper/parser/stmt.py", line 103, in parse_If
    add_on = [parse_body(self.stmt.orelse, self.context)]
  File "vyper/parser/stmt.py", line 645, in parse_body
    lll = parse_stmt(stmt, context)
  File "vyper/parser/stmt.py", line 635, in parse_stmt
    return Stmt(stmt, context).lll_node
  File "vyper/parser/stmt.py", line 41, in __init__
    self.lll_node = fn()
  File "vyper/parser/stmt.py", line 110, in parse_If
    body = ["if", test_expr, parse_body(self.stmt.body, self.context)] + add_on
  File "vyper/parser/stmt.py", line 645, in parse_body
    lll = parse_stmt(stmt, context)
  File "vyper/parser/stmt.py", line 635, in parse_stmt
    return Stmt(stmt, context).lll_node
  File "vyper/parser/stmt.py", line 41, in __init__
    self.lll_node = fn()
  File "vyper/parser/stmt.py", line 94, in parse_Assign
    target = self._get_target(self.stmt.target)
  File "vyper/parser/stmt.py", line 627, in _get_target
    target = Expr.parse_variable_location(target, self.context)
  File "vyper/parser/expr.py", line 978, in parse_variable_location
    o = cls(expr, context).lll_node
  File "vyper/parser/expr.py", line 200, in __init__
    self.lll_node = fn()
  File "vyper/parser/expr.py", line 413, in parse_Subscript
    index = Expr.parse_value_expr(self.expr.slice.value, self.context)
  File "vyper/parser/expr.py", line 973, in parse_value_expr
    return unwrap_location(cls(expr, context).lll_node)
  File "vyper/parser/expr.py", line 202, in __init__
    raise TypeCheckFailure(f"{type(node).__name__} node did not produce LLL")
TypeCheckFailure: BinOp node did not produce LLL
```
The code that failed:
```python
MAXIMUM_STRATEGIES: constant(uint256) = 20
withdrawalQueue: public(address[MAXIMUM_STRATEGIES])

@external
def organizeWithdrawalQueue():
    offset: uint256 = 0
    for idx in range(MAXIMUM_STRATEGIES):
        strategy: address = self.withdrawalQueue[idx]
        if strategy == ZERO_ADDRESS:
            offset += 1  # how many values we need to shift, always `<= idx`
        elif offset > 0:
            self.withdrawalQueue[idx-offset] = strategy
            self.withdrawalQueue[idx] = ZERO_ADDRESS
```

### How can it be fixed?
This line is the problem
```python
self.withdrawalQueue[idx-offset] = strategy
```
Remove the subtraction of `offset` and it compiles


Discussion:

Resolving patch:
diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py
--- a/vyper/ast/nodes.py
+++ b/vyper/ast/nodes.py
@@ -786,7 +786,10 @@ class NameConstant(Constant):
 
 
 class Name(VyperNode):
-    __slots__ = ("id",)
+    __slots__ = (
+        "id",
+        "_type",
+    )
 
 
 class Expr(VyperNode):
diff --git a/vyper/context/validation/local.py b/vyper/context/validation/local.py
--- a/vyper/context/validation/local.py
+++ b/vyper/context/validation/local.py
@@ -35,6 +35,7 @@
     FunctionDeclarationException,
     ImmutableViolation,
     InvalidLiteral,
+    InvalidOperation,
     InvalidType,
     IteratorException,
     NonPayableViolation,
@@ -369,8 +370,11 @@ def visit_For(self, node):
                 try:
                     for n in node.body:
                         self.visit(n)
+                    # attach type information to allow non `int128` types in `vyper.parser.stmt`
+                    # this is a temporary solution until `vyper.parser` has been refactored
+                    node.target._type = type_._id
                     return
-                except TypeMismatch as exc:
+                except (TypeMismatch, InvalidOperation) as exc:
                     for_loop_exceptions.append(exc)
 
         if len(set(str(i) for i in for_loop_exceptions)) == 1:
diff --git a/vyper/parser/stmt.py b/vyper/parser/stmt.py
--- a/vyper/parser/stmt.py
+++ b/vyper/parser/stmt.py
@@ -231,6 +231,10 @@ def parse_For(self):
         if not 0 < len(self.stmt.iter.args) < 3:
             return
 
+        # attempt to use the type specified by type checking, fall back to `int128`
+        # this is a stopgap solution to allow uint256 - it will be properly solved
+        # once we refactor `vyper.parser`
+        iter_typ = self.stmt.target.get("_type") or "int128"
         block_scope_id = id(self.stmt)
         with self.context.make_blockscope(block_scope_id):
             # Get arg0
@@ -240,15 +244,15 @@ def parse_For(self):
             # Type 1 for, e.g. for i in range(10): ...
             if num_of_args == 1:
                 arg0_val = self._get_range_const_value(arg0)
-                start = LLLnode.from_list(0, typ="int128", pos=getpos(self.stmt))
+                start = LLLnode.from_list(0, typ=iter_typ, pos=getpos(self.stmt))
                 rounds = arg0_val
 
             # Type 2 for, e.g. for i in range(100, 110): ...
             elif self._check_valid_range_constant(self.stmt.iter.args[1], raise_exception=False)[0]:
                 arg0_val = self._get_range_const_value(arg0)
                 arg1_val = self._get_range_const_value(self.stmt.iter.args[1])
-                start = LLLnode.from_list(arg0_val, typ="int128", pos=getpos(self.stmt))
-                rounds = LLLnode.from_list(arg1_val - arg0_val, typ="int128", pos=getpos(self.stmt))
+                start = LLLnode.from_list(arg0_val, typ=iter_typ, pos=getpos(self.stmt))
+                rounds = LLLnode.from_list(arg1_val - arg0_val, typ=iter_typ, pos=getpos(self.stmt))
 
             # Type 3 for, e.g. for i in range(x, x + 10): ...
             else:
@@ -261,7 +265,7 @@ def parse_For(self):
                 return
 
             varname = self.stmt.target.id
-            pos = self.context.new_variable(varname, BaseType("int128"), pos=getpos(self.stmt))
+            pos = self.context.new_variable(varname, BaseType(iter_typ), pos=getpos(self.stmt))
             self.context.forvars[varname] = True
             lll_node = LLLnode.from_list(
                 ["repeat", pos, start, rounds, parse_body(self.stmt.body, self.context)],


Test patch:
diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py
--- a/tests/parser/features/iteration/test_for_in_list.py
+++ b/tests/parser/features/iteration/test_for_in_list.py
@@ -506,6 +506,19 @@ def test_for() -> int128:
     """,
         TypeMismatch,
     ),
+    (
+        """
+@external
+def test_for() -> int128:
+    a: int128 = 0
+    b: uint256 = 0
+    for i in range(5):
+        a = i
+        b = i
+    return a
+    """,
+        TypeMismatch,
+    ),
 ]
 
 
diff --git a/tests/parser/features/iteration/test_repeater.py b/tests/parser/features/iteration/test_repeater.py
--- a/tests/parser/features/iteration/test_repeater.py
+++ b/tests/parser/features/iteration/test_repeater.py
@@ -1,3 +1,6 @@
+import pytest
+
+
 def test_basic_repeater(get_contract_with_gas_estimation):
     basic_repeater = """
 @external
@@ -47,30 +50,32 @@ def repeat() -> int128:
     assert c.repeat() == 666666
 
 
-def test_offset_repeater(get_contract_with_gas_estimation):
-    offset_repeater = """
+@pytest.mark.parametrize("typ", ["int128", "uint256"])
+def test_offset_repeater(get_contract_with_gas_estimation, typ):
+    offset_repeater = f"""
 @external
-def sum() -> int128:
-    out: int128 = 0
+def sum() -> {typ}:
+    out: {typ} = 0
     for i in range(80, 121):
         out = out + i
-    return(out)
+    return out
     """
 
     c = get_contract_with_gas_estimation(offset_repeater)
     assert c.sum() == 4100
 
 
-def test_offset_repeater_2(get_contract_with_gas_estimation):
-    offset_repeater_2 = """
+@pytest.mark.parametrize("typ", ["int128", "uint256"])
+def test_offset_repeater_2(get_contract_with_gas_estimation, typ):
+    offset_repeater_2 = f"""
 @external
-def sum(frm: int128, to: int128) -> int128:
-    out: int128 = 0
+def sum(frm: {typ}, to: {typ}) -> {typ}:
+    out: {typ} = 0
     for i in range(frm, frm + 101):
         if i == to:
             break
         out = out + i
-    return(out)
+    return out
     """
 
     c = get_contract_with_gas_estimation(offset_repeater_2)
@@ -95,61 +100,64 @@ def foo() -> bool:
     assert c.foo() is True
 
 
-def test_return_inside_repeater(get_contract):
-    code = """
+@pytest.mark.parametrize("typ", ["int128", "uint256"])
+def test_return_inside_repeater(get_contract, typ):
+    code = f"""
 @internal
-def _final(a: int128) -> int128:
+def _final(a: {typ}) -> {typ}:
     for i in range(10):
         if i > a:
             return i
-    return -42
+    return 31337
 
 @internal
-def _middle(a: int128) -> int128:
-    b: int128 = self._final(a)
+def _middle(a: {typ}) -> {typ}:
+    b: {typ} = self._final(a)
     return b
 
 @external
-def foo(a: int128) -> int128:
-    b: int128 = self._middle(a)
+def foo(a: {typ}) -> {typ}:
+    b: {typ} = self._middle(a)
     return b
     """
 
     c = get_contract(code)
     assert c.foo(6) == 7
-    assert c.foo(100) == -42
+    assert c.foo(100) == 31337
 
 
-def test_return_inside_nested_repeater(get_contract):
-    code = """
+@pytest.mark.parametrize("typ", ["int128", "uint256"])
+def test_return_inside_nested_repeater(get_contract, typ):
+    code = f"""
 @internal
-def _final(a: int128) -> int128:
+def _final(a: {typ}) -> {typ}:
     for i in range(10):
         for x in range(10):
             if i + x > a:
                 return i + x
-    return -42
+    return 31337
 
 @internal
-def _middle(a: int128) -> int128:
-    b: int128 = self._final(a)
+def _middle(a: {typ}) -> {typ}:
+    b: {typ} = self._final(a)
     return b
 
 @external
-def foo(a: int128) -> int128:
-    b: int128 = self._middle(a)
+def foo(a: {typ}) -> {typ}:
+    b: {typ} = self._middle(a)
     return b
     """
 
     c = get_contract(code)
     assert c.foo(14) == 15
-    assert c.foo(100) == -42
+    assert c.foo(100) == 31337
 
 
-def test_breaks_and_returns_inside_nested_repeater(get_contract):
-    code = """
+@pytest.mark.parametrize("typ", ["int128", "uint256"])
+def test_breaks_and_returns_inside_nested_repeater(get_contract, typ):
+    code = f"""
 @internal
-def _final(a: int128) -> int128:
+def _final(a: {typ}) -> {typ}:
     for i in range(10):
         for x in range(10):
             if a < 2:
@@ -159,20 +167,20 @@ def _final(a: int128) -> int128:
             break
         return 31337
 
-    return -42
+    return 666
 
 @internal
-def _middle(a: int128) -> int128:
-    b: int128 = self._final(a)
+def _middle(a: {typ}) -> {typ}:
+    b: {typ} = self._final(a)
     return b
 
 @external
-def foo(a: int128) -> int128:
-    b: int128 = self._middle(a)
+def foo(a: {typ}) -> {typ}:
+    b: {typ} = self._middle(a)
     return b
     """
 
     c = get_contract(code)
     assert c.foo(100) == 6
-    assert c.foo(1) == -42
+    assert c.foo(1) == 666
     assert c.foo(0) == 31337