Repository: vyperlang/vyper

Issue:
constant function allows to call non-constant local function
### What's your issue about?

```
f:num

@public
def a (x:num)->num:
    self.f = 100
    return x+5

@constant
@public
def b():
    p = self.a(10)
```
This program should be rejected since `b` is a constant function. It should not call `a` which modifies the storage.

#### Cute Animal Picture

![image](https://user-images.githubusercontent.com/5641590/35161536-24dc6a48-fd06-11e7-843d-0a1b20feea76.png)




Discussion:
Good catch, as always ;) awesome picture!
Starting on this ;)

Resolving patch:
diff --git a/vyper/parser/expr.py b/vyper/parser/expr.py
--- a/vyper/parser/expr.py
+++ b/vyper/parser/expr.py
@@ -1,11 +1,12 @@
 import ast
 
 from vyper.exceptions import (
+    ConstancyViolationException,
+    InvalidLiteralException,
+    NonPayableViolationException,
     StructureException,
     TypeMismatchException,
     VariableDeclarationException,
-    InvalidLiteralException,
-    NonPayableViolationException,
 )
 from .parser_utils import LLLnode
 from .parser_utils import (
@@ -461,7 +462,12 @@ def call(self):
             if method_name not in self.context.sigs['self']:
                 raise VariableDeclarationException("Function not declared yet (reminder: functions cannot "
                                                    "call functions later in code than themselves): %s" % self.expr.func.attr)
-            sig = self.context.sigs['self'][self.expr.func.attr]
+
+            sig = self.context.sigs['self'][method_name]
+            if self.context.is_constant and not sig.const:
+                raise ConstancyViolationException(
+                    "May not call non-constant function '%s' within a constant function." % (method_name)
+                )
             add_gas = self.context.sigs['self'][method_name].gas  # gas of call
             inargs, inargsize = pack_arguments(sig, [Expr(arg, self.context).lll_node for arg in self.expr.args], self.context)
             output_placeholder = self.context.new_placeholder(typ=sig.output_type)
diff --git a/vyper/parser/stmt.py b/vyper/parser/stmt.py
--- a/vyper/parser/stmt.py
+++ b/vyper/parser/stmt.py
@@ -162,8 +162,13 @@ def call(self):
             if method_name not in self.context.sigs['self']:
                 raise VariableDeclarationException("Function not declared yet (reminder: functions cannot "
                                                     "call functions later in code than themselves): %s" % self.stmt.func.attr)
+            sig = self.context.sigs['self'][method_name]
+            if self.context.is_constant and not sig.const:
+                raise ConstancyViolationException(
+                    "May not call non-constant function '%s' within a constant function." % (method_name)
+                )
             add_gas = self.context.sigs['self'][method_name].gas
-            inargs, inargsize = pack_arguments(self.context.sigs['self'][self.stmt.func.attr],
+            inargs, inargsize = pack_arguments(sig,
                                                 [Expr(arg, self.context).lll_node for arg in self.stmt.args],
                                                 self.context)
             return LLLnode.from_list(['assert', ['call', ['gas'], ['address'], 0, inargs, inargsize, 0, 0]],


Test patch:
diff --git a/tests/parser/exceptions/test_constancy_exception.py b/tests/parser/exceptions/test_constancy_exception.py
--- a/tests/parser/exceptions/test_constancy_exception.py
+++ b/tests/parser/exceptions/test_constancy_exception.py
@@ -52,6 +52,31 @@ def foo() -> num:
 @public
 def foo(x: num):
     x = 5
+    """,
+    """
+f:num
+
+@public
+def a (x:num)->num:
+    self.f = 100
+    return x+5
+
+@constant
+@public
+def b():
+    p: num = self.a(10)
+    """,
+    """
+f:num
+
+@public
+def a (x:num):
+    self.f = 100
+
+@constant
+@public
+def b():
+    self.a(10)
     """
 ]