Repository: vyperlang/vyper

Issue:
VIP: Allow environment variables and constants as "default value" in public functions
There are a number of Uniswap functions that have a recipient field. It would be great to default this field to msg.sender like so:

```python
@public
@payable
def ethToTokenSwap(minTokens: uint256, recipient: address = msg.sender):
```

Currently I have to do something along these lines:
```python
@public
@payable
def ethToTokenSwap(minTokens: uint256, recipient: address = 0x000...000):
   if(recipient == ZERO_ADDRESS):
      recipient = msg.sender
```

which isn't the worst but it would be great to have msg.sender, msg.value, and constants available here.


Discussion:
I'm a fan of this idea 👍 
So, allow constants and environment variables? I'm on board with that too

Resolving patch:
diff --git a/vyper/parser/expr.py b/vyper/parser/expr.py
--- a/vyper/parser/expr.py
+++ b/vyper/parser/expr.py
@@ -53,6 +53,23 @@
     string_to_bytes,
 )
 
+# var name: (lllnode, type)
+BUILTIN_CONSTANTS = {
+    'EMPTY_BYTES32': (0, 'bytes32'),
+    'ZERO_ADDRESS': (0, 'address'),
+    'MAX_INT128': (SizeLimits.MAXNUM, 'int128'),
+    'MIN_INT128': (SizeLimits.MINNUM, 'int128'),
+    'MAX_DECIMAL': (SizeLimits.MAXDECIMAL, 'decimal'),
+    'MIN_DECIMAL': (SizeLimits.MINDECIMAL, 'decimal'),
+    'MAX_UINT256': (SizeLimits.MAX_UINT256, 'uint256'),
+}
+
+ENVIRONMENT_VARIABLES = (
+    "block",
+    "msg",
+    "tx",
+)
+
 
 class Expr(object):
     # TODO: Once other refactors are made reevaluate all inline imports
@@ -220,43 +237,6 @@ def constants(self):
 
     # Variable names
     def variables(self):
-        builtin_constants = {
-            'EMPTY_BYTES32': LLLnode.from_list(
-                [0],
-                typ=BaseType('bytes32', None, is_literal=True),
-                pos=getpos(self.expr)
-            ),
-            'ZERO_ADDRESS': LLLnode.from_list(
-                [0],
-                typ=BaseType('address', None, is_literal=True),
-                pos=getpos(self.expr)
-            ),
-            'MAX_INT128': LLLnode.from_list(
-                [SizeLimits.MAXNUM],
-                typ=BaseType('int128', None, is_literal=True),
-                pos=getpos(self.expr)
-            ),
-            'MIN_INT128': LLLnode.from_list(
-                [SizeLimits.MINNUM],
-                typ=BaseType('int128', None, is_literal=True),
-                pos=getpos(self.expr)
-            ),
-            'MAX_DECIMAL': LLLnode.from_list(
-                [SizeLimits.MAXDECIMAL],
-                typ=BaseType('decimal', None, is_literal=True),
-                pos=getpos(self.expr)
-            ),
-            'MIN_DECIMAL': LLLnode.from_list(
-                [SizeLimits.MINDECIMAL],
-                typ=BaseType('decimal', None, is_literal=True),
-                pos=getpos(self.expr)
-            ),
-            'MAX_UINT256': LLLnode.from_list(
-                [SizeLimits.MAX_UINT256],
-                typ=BaseType('uint256', None, is_literal=True),
-                pos=getpos(self.expr)
-            ),
-        }
 
         if self.expr.id == 'self':
             return LLLnode.from_list(['address'], typ='address', pos=getpos(self.expr))
@@ -270,8 +250,13 @@ def variables(self):
                 annotation=self.expr.id,
                 mutable=var.mutable,
             )
-        elif self.expr.id in builtin_constants:
-            return builtin_constants[self.expr.id]
+
+        elif self.expr.id in BUILTIN_CONSTANTS:
+            obj, typ = BUILTIN_CONSTANTS[self.expr.id]
+            return LLLnode.from_list(
+                [obj],
+                typ=BaseType(typ, None, is_literal=True),
+                pos=getpos(self.expr))
         elif self.context.constants.ast_is_constant(self.expr):
             return self.context.constants.get_constant(self.expr.id, self.context)
         else:
@@ -329,7 +314,7 @@ def attribute(self):
                 annotation='self.' + self.expr.attr,
             )
         # Reserved keywords
-        elif isinstance(self.expr.value, ast.Name) and self.expr.value.id in ("msg", "block", "tx"):
+        elif isinstance(self.expr.value, ast.Name) and self.expr.value.id in ENVIRONMENT_VARIABLES:
             key = self.expr.value.id + "." + self.expr.attr
             if key == "msg.sender":
                 if self.context.is_private:
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
@@ -2,7 +2,10 @@
     Counter,
 )
 
-from vyper import ast
+from vyper import (
+    ast,
+    parser,
+)
 from vyper.ast_utils import (
     to_python_ast,
 )
@@ -468,9 +471,16 @@ def validate_return_statement_balance(self):
 
 
 def validate_default_values(node):
+    if isinstance(node, ast.Name) and node.id in parser.expr.BUILTIN_CONSTANTS:
+        return
+    if isinstance(node, ast.Attribute) and node.value.id in parser.expr.ENVIRONMENT_VARIABLES:
+        return
     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)
+        raise FunctionDeclarationException(
+            "Default value must be a literal, built-in constant, or environment variable.",
+            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
@@ -6,6 +6,8 @@
 from vyper.exceptions import (
     FunctionDeclarationException,
     InvalidLiteralException,
+    NonPayableViolationException,
+    ParserException,
     TypeMismatchException,
 )
 
@@ -158,6 +160,44 @@ def callMeMaybe() -> (bytes[100], uint256, bytes[20]):
     assert c.callMeMaybe() == [b'here is my number', 555123456, b'baby']
 
 
+def test_builtin_constants_as_default(get_contract):
+    code = """
+@public
+def foo(a: int128 = MIN_INT128, b: int128 = MAX_INT128) -> (int128, int128):
+    return a, b
+    """
+    c = get_contract(code)
+    assert c.foo() == [-(2**127), 2**127-1]
+    assert c.foo(31337) == [31337, 2**127-1]
+    assert c.foo(13, 42) == [13, 42]
+
+
+def test_environment_vars_as_default(get_contract):
+    code = """
+xx: uint256(wei)
+
+@public
+@payable
+def foo(a: uint256(wei) = msg.value) -> bool:
+    self.xx += a
+    return True
+
+@public
+def bar() -> uint256(wei):
+    return self.xx
+
+@public
+def get_balance() -> uint256(wei):
+    return self.balance
+    """
+    c = get_contract(code)
+    c.foo(transact={'value': 31337})
+    assert c.bar() == 31337
+    c.foo(666, transact={'value': 9001})
+    assert c.bar() == 31337 + 666
+    assert c.get_balance() == 31337 + 9001
+
+
 PASSING_CONTRACTS = [
     """
 @public
@@ -187,7 +227,16 @@ def foo(a: bytes[6] = "potato"): pass
     """
 @public
 def foo(a: decimal = 3.14, b: decimal[2] = [1.337, 2.69]): pass
+    """,
+    """
+@public
+def foo(a: address = msg.sender, b: address[3] = [msg.sender, tx.origin, block.coinbase]): pass
+    """,
     """
+@public
+@payable
+def foo(a: uint256(wei) = msg.value): pass
+    """,
 ]
 
 
@@ -253,6 +302,16 @@ def foo(a: uint256[2] = [2, self.x]): pass
 @public
 def foo(a: uint256 = 2**8): pass
      """, FunctionDeclarationException),
+    ("""
+# msg.value in a nonpayable
+@public
+def foo(a: uint256 = msg.value): pass
+""", NonPayableViolationException),
+    ("""
+# msg.sender in a private function
+@private
+def foo(a: address = msg.sender): pass
+    """, ParserException),
 ]