Repository: vyperlang/vyper

Issue:
Default parameters do not get properly typechecked
### Version Information

* vyper Version (output of `vyper --version`): f6cf2da3d542bf5a325ed28b7047a1ac25bb7a3a
* OS: linux
* Python Version (output of `python --version`): `Python 3.6.7`

### What's your issue about?
The following function passes the type checker.
```python
@public 
def foo(x: uint256 = -1):
    pass
```

### How can it be fixed?
Use the type checker on default function parameters


Discussion:
Yup, also picked this up checking #1466 ;)

Resolving patch:
diff --git a/vyper/parser/parser_utils.py b/vyper/parser/parser_utils.py
--- a/vyper/parser/parser_utils.py
+++ b/vyper/parser/parser_utils.py
@@ -535,6 +535,14 @@ def make_setter(left, right, location, pos, in_function_call=False):
             pos,
             in_function_call=in_function_call,
         )
+        # TODO this overlaps a type check in parser.stmt.Stmt._check_valid_assign
+        # and should be examined during a refactor (@iamdefinitelyahuman)
+        if 'int' in left.typ.typ and isinstance(right.value, int):
+            if not SizeLimits.in_bounds(left.typ.typ, right.value):
+                raise InvalidLiteralException(
+                    f"Number out of range for {left.typ}: {right.value}",
+                    pos
+                )
         if location == 'storage':
             return LLLnode.from_list(['sstore', left, right], typ=None)
         elif location == 'memory':
diff --git a/vyper/signatures/function_signature.py b/vyper/signatures/function_signature.py
--- a/vyper/signatures/function_signature.py
+++ b/vyper/signatures/function_signature.py
@@ -170,9 +170,7 @@ def from_definition(cls,
 
         # Validate default values.
         for default_value in getattr(code.args, 'defaults', []):
-            allowed_types = (ast.Num, ast.Str, ast.Bytes, ast.List, ast.NameConstant)
-            if not isinstance(default_value, allowed_types):
-                raise FunctionDeclarationException("Default parameter values have to be literals.")
+            validate_default_values(default_value)
 
         # Determine the arguments, expects something of the form def foo(arg1:
         # int128, arg2: int128 ...
@@ -467,3 +465,12 @@ def validate_return_statement_balance(self):
         # Run balanced return statement check.
         UnmatchedReturnChecker().visit(to_python_ast(self.func_ast_code))
         EnsureSingleExitChecker().visit(to_python_ast(self.func_ast_code))
+
+
+def validate_default_values(node):
+    allowed_types = (ast.Num, ast.Str, ast.Bytes, ast.List, ast.NameConstant)
+    if not isinstance(node, allowed_types):
+        raise FunctionDeclarationException("Default parameter values have to be literals.", node)
+    if isinstance(node, ast.List):
+        for n in node.elts:
+            validate_default_values(n)


Test patch:
diff --git a/tests/parser/functions/test_default_parameters.py b/tests/parser/functions/test_default_parameters.py
--- a/tests/parser/functions/test_default_parameters.py
+++ b/tests/parser/functions/test_default_parameters.py
@@ -1,7 +1,12 @@
 import pytest
 
+from vyper.compiler import (
+    compile_code,
+)
 from vyper.exceptions import (
     FunctionDeclarationException,
+    InvalidLiteralException,
+    TypeMismatchException,
 )
 
 
@@ -153,12 +158,105 @@ def callMeMaybe() -> (bytes[100], uint256, bytes[20]):
     assert c.callMeMaybe() == [b'here is my number', 555123456, b'baby']
 
 
-def test_default_param_literal(get_contract):
-    code = """
+PASSING_CONTRACTS = [
+    """
+@public
+def foo(a: bool = True, b: bool[2] = [True, False]): pass
+    """,
+    """
+@public
+def foo(
+    a: address = 0x0c04792e92e6b2896a18568fD936781E9857feB7,
+    b: address[2] = [
+        0x0c04792e92e6b2896a18568fD936781E9857feB7,
+        0x0c04792e92e6b2896a18568fD936781E9857feB7
+    ]): pass
+    """,
+    """
+@public
+def foo(a: uint256 = 12345, b: uint256[2] = [31337, 42]): pass
+    """,
+    """
+@public
+def foo(a: int128 = -31, b: int128[2] = [64, -46]): pass
+    """,
+    """
+@public
+def foo(a: bytes[6] = "potato"): pass
+    """,
+    """
+@public
+def foo(a: decimal = 3.14, b: decimal[2] = [1.337, 2.69]): pass
+    """
+]
+
+
+@pytest.mark.parametrize('code', PASSING_CONTRACTS)
+def test_good_default_params(code):
+    assert compile_code(code)
+
+
+FAILING_CONTRACTS = [
+    ("""
+# default params must be literals
 x: int128
 
 @public
 def foo(xx: int128, y: int128 = xx): pass
-    """
-    with pytest.raises(FunctionDeclarationException):
-        get_contract(code)
+    """, FunctionDeclarationException),
+    ("""
+# value out of range for uint256
+@public
+def foo(a: uint256 = -1): pass
+    """, InvalidLiteralException),
+    ("""
+# value out of range for int128
+@public
+def foo(a: int128 = 170141183460469231731687303715884105728): pass
+    """, TypeMismatchException),
+    ("""
+# value out of range for uint256 array
+@public
+def foo(a: uint256[2] = [13, -42]): pass
+     """, InvalidLiteralException),
+    ("""
+# value out of range for int128 array
+@public
+def foo(a: int128[2] = [12, 170141183460469231731687303715884105728]): pass
+    """, TypeMismatchException),
+    ("""
+# array type mismatch
+@public
+def foo(a: uint256[2] = [12, True]): pass
+    """, TypeMismatchException),
+    ("""
+# wrong length
+@public
+def foo(a: uint256[2] = [1, 2, 3]): pass
+    """, TypeMismatchException),
+    ("""
+# default params must be literals
+x: uint256
+
+@public
+def foo(a: uint256 = self.x): pass
+     """, FunctionDeclarationException),
+    ("""
+# default params must be literals inside array
+x: uint256
+
+@public
+def foo(a: uint256[2] = [2, self.x]): pass
+     """, FunctionDeclarationException),
+    ("""
+# default params still must be literals
+@public
+def foo(a: uint256 = 2**8): pass
+     """, FunctionDeclarationException),
+]
+
+
+@pytest.mark.parametrize('failing_contract', FAILING_CONTRACTS)
+def test_bad_default_params(failing_contract, assert_compile_failed):
+    code, exc = failing_contract
+    assert_compile_failed(lambda: compile_code(code), exc)
diff --git a/tests/parser/syntax/test_ann_assign.py b/tests/parser/syntax/test_ann_assign.py
--- a/tests/parser/syntax/test_ann_assign.py
+++ b/tests/parser/syntax/test_ann_assign.py
@@ -7,6 +7,7 @@
     compiler,
 )
 from vyper.exceptions import (
+    InvalidLiteralException,
     StructureException,
     TypeMismatchException,
     VariableDeclarationException,
@@ -77,7 +78,25 @@ def foo() -> int128:
 def foo() -> int128:
     s: S = S({b: 1.2, c: 1, d: 33, e: 55})
     return s.a
-    """, TypeMismatchException)
+    """, TypeMismatchException),
+    ("""
+@public
+def foo() -> bool:
+    a: uint256 = -1
+    return True
+""", InvalidLiteralException),
+    ("""
+@public
+def foo() -> bool:
+    a: uint256[2] = [13, -42]
+    return True
+""", InvalidLiteralException),
+    ("""
+@public
+def foo() -> bool:
+    a: int128 = 170141183460469231731687303715884105728
+    return True
+""", TypeMismatchException),
 ]