Repository: vyperlang/vyper

Issue:
@nonreentrant should not compile on constant functions
Right now this compiles:

```python
@public
@constant
@nonreentrant('lock')
def tokenAddress() -> address:
    return self.token
```

however reentrancy should never be a problem in constant functions


Discussion:
Agreed, this should be a compiler error.
I'm currently looking into this.  Will try to have a PR in next day or so.
Actually, this is a pretty easy fix.  PR incoming.

Resolving patch:
diff --git a/vyper/parser/parser.py b/vyper/parser/parser.py
--- a/vyper/parser/parser.py
+++ b/vyper/parser/parser.py
@@ -96,7 +96,7 @@ def parse_external_contracts(external_contracts, global_ctx):
             sig = FunctionSignature.from_definition(
                 _def,
                 contract_def=True,
-                constant=constant,
+                constant_override=constant,
                 custom_structs=global_ctx._structs,
                 constants=global_ctx._constants
             )
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
@@ -157,7 +157,7 @@ def from_definition(cls,
                         custom_structs=None,
                         contract_def=False,
                         constants=None,
-                        constant=False):
+                        constant_override=False):
         if not custom_structs:
             custom_structs = {}
 
@@ -219,8 +219,13 @@ def from_definition(cls,
             else:
                 mem_pos += get_size_of_type(parsed_type) * 32
 
-        # Apply decorators
-        const, payable, private, public, nonreentrant_key = False, False, False, False, ''
+        const = constant_override
+        payable = False
+        private = False
+        public = False
+        nonreentrant_key = ''
+
+        # Update function properties from decorators
         for dec in code.decorator_list:
             if isinstance(dec, ast.Name) and dec.id == "constant":
                 const = True
@@ -258,10 +263,9 @@ def from_definition(cls,
                 "Function visibility must be declared (@public or @private)",
                 code,
             )
-        if constant and nonreentrant_key:
+        if const and nonreentrant_key:
             raise StructureException("@nonreentrant makes no sense on a @constant function.", code)
-        if constant:
-            const = True
+
         # Determine the return type and whether or not it's constant. Expects something
         # of the form:
         # def foo(): ...


Test patch:
diff --git a/tests/signatures/test_invalid_function_decorators.py b/tests/signatures/test_invalid_function_decorators.py
new file mode 100644
--- /dev/null
+++ b/tests/signatures/test_invalid_function_decorators.py
@@ -0,0 +1,24 @@
+import pytest
+
+from vyper import (
+    compiler,
+)
+from vyper.exceptions import (
+    StructureException,
+)
+
+FAILING_CONTRACTS = [
+    """
+@public
+@constant
+@nonreentrant('lock')
+def nonreentrant_foo() -> uint256:
+    return 1
+    """,
+]
+
+
+@pytest.mark.parametrize('failing_contract_code', FAILING_CONTRACTS)
+def test_invalid_function_decorators(failing_contract_code):
+    with pytest.raises(StructureException):
+        compiler.compile_code(failing_contract_code)