Repository: vyperlang/vyper

Issue:
VIP: re-enable msg.* environment variables in internal functions
## Motivation
There used to be confusion as to the value of `msg.*` when we implemented internal functions as call-to-self, so we disabled `msg.*` in internal functions in https://github.com/vyperlang/vyper/issues/1199. However, since then internal functions are implemented just using JUMPs, so the EVM execution context doesn't change. In other words, `msg.sender` is the same no matter if it's used in an internal or external function, so we can lift this restriction.

## References
- #901
- #1199

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/)



Discussion:

Resolving patch:
diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py
--- a/vyper/codegen/expr.py
+++ b/vyper/codegen/expr.py
@@ -430,9 +430,9 @@ def parse_Attribute(self):
             isinstance(self.expr.value, vy_ast.Name) and self.expr.value.id in ENVIRONMENT_VARIABLES
         ):
             key = f"{self.expr.value.id}.{self.expr.attr}"
-            if key == "msg.sender" and not self.context.is_internal:
+            if key == "msg.sender":
                 return LLLnode.from_list(["caller"], typ="address", pos=getpos(self.expr))
-            elif key == "msg.data" and not self.context.is_internal:
+            elif key == "msg.data":
                 # This adhoc node will be replaced with a valid node in `Slice/Len.build_LLL`
                 return LLLnode.from_list(["~calldata"], typ=ByteArrayType(0))
             elif key == "msg.value" and self.context.is_payable:
diff --git a/vyper/semantics/validation/local.py b/vyper/semantics/validation/local.py
--- a/vyper/semantics/validation/local.py
+++ b/vyper/semantics/validation/local.py
@@ -25,7 +25,7 @@
 from vyper.semantics.namespace import get_namespace
 from vyper.semantics.types.abstract import IntegerAbstractType
 from vyper.semantics.types.bases import DataLocation
-from vyper.semantics.types.function import ContractFunction, FunctionVisibility, StateMutability
+from vyper.semantics.types.function import ContractFunction, StateMutability
 from vyper.semantics.types.indexable.sequence import (
     ArrayDefinition,
     DynamicArrayDefinition,
@@ -166,14 +166,6 @@ def __init__(
         self.expr_visitor = _LocalExpressionVisitor()
         namespace.update(self.func.arguments)
 
-        if self.func.visibility is FunctionVisibility.INTERNAL:
-            node_list = fn_node.get_descendants(
-                vy_ast.Attribute, {"value.id": "msg", "attr": {"data", "sender"}}
-            )
-            if node_list:
-                raise StateAccessViolation(
-                    f"msg.{node_list[0].attr} is not allowed in internal functions", node_list[0]
-                )
         if self.func.mutability == StateMutability.PURE:
             node_list = fn_node.get_descendants(
                 vy_ast.Attribute,


Test patch:
diff --git a/tests/parser/features/decorators/test_private.py b/tests/parser/features/decorators/test_private.py
--- a/tests/parser/features/decorators/test_private.py
+++ b/tests/parser/features/decorators/test_private.py
@@ -417,14 +417,36 @@ def __default__():
     assert w3.eth.get_balance(c.address) == w3.toWei(0.95, "ether")
 
 
-def test_private_msg_sender(get_contract, assert_compile_failed):
+def test_private_msg_sender(get_contract, w3):
     code = """
+event Addr:
+    addr: address
+
 @internal
+@view
 def _whoami() -> address:
     return msg.sender
+
+@external
+@view
+def i_am_me() -> bool:
+    return msg.sender == self._whoami()
+
+@external
+@view
+def whoami() -> address:
+    log Addr(self._whoami())
+    return self._whoami()
     """
 
-    assert_compile_failed(lambda: get_contract(code))
+    c = get_contract(code)
+    assert c.i_am_me()
+
+    addr = w3.eth.accounts[1]
+    txhash = c.whoami(transact={"from": addr})
+    receipt = w3.eth.wait_for_transaction_receipt(txhash)
+    logged_addr = w3.toChecksumAddress(receipt.logs[0].data[-40:])
+    assert logged_addr == addr, "oh no"
 
 
 def test_nested_static_params_only(get_contract, assert_tx_failed):
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
@@ -267,6 +267,10 @@ def foo(a: decimal = 3.14, b: decimal[2] = [1.337, 2.69]): pass
     """,
     """
 @external
+def foo(a: address = msg.sender, b: address[3] = [msg.sender, tx.origin, block.coinbase]): pass
+    """,
+    """
+@internal
 def foo(a: address = msg.sender, b: address[3] = [msg.sender, tx.origin, block.coinbase]): pass
     """,
     """
@@ -373,14 +377,6 @@ def foo(a: uint256 = msg.value): pass
 """,
         NonPayableViolation,
     ),
-    (
-        """
-# msg.sender in a private function
-@internal
-def foo(a: address = msg.sender): pass
-    """,
-        StateAccessViolation,
-    ),
 ]
 
 
diff --git a/tests/parser/syntax/test_msg_data.py b/tests/parser/syntax/test_msg_data.py
--- a/tests/parser/syntax/test_msg_data.py
+++ b/tests/parser/syntax/test_msg_data.py
@@ -2,7 +2,7 @@
 from eth_tester.exceptions import TransactionFailed
 
 from vyper import compiler
-from vyper.exceptions import StateAccessViolation, SyntaxException, TypeMismatch
+from vyper.exceptions import SyntaxException, TypeMismatch
 
 
 def test_variable_assignment(get_contract, keccak):
@@ -48,6 +48,19 @@ def foo(bar: uint256) -> Bytes[36]:
     assert contract.foo(42).hex() == expected_result
 
 
+@pytest.mark.parametrize("bar", [0, 1, 42, 2 ** 256 - 1])
+def test_calldata_private(get_contract, bar):
+    code = """
+@external
+def foo(bar: uint256) -> uint256:
+    data: Bytes[32] = slice(msg.data, 4, 32)
+    return convert(data, uint256)
+    """
+    c = get_contract(code)
+
+    assert c.foo(bar) == bar
+
+
 def test_memory_pointer_advances_appropriately(get_contract, keccak):
     code = """
 @external
@@ -119,14 +132,6 @@ def foo() -> uint256:
     ),
     (
         """
-@internal
-def foo() -> Bytes[4]:
-    return slice(msg.data, 0, 4)
-    """,
-        StateAccessViolation,
-    ),
-    (
-        """
 @external
 def foo(bar: uint256) -> bytes32:
     ret_val: bytes32 = slice(msg.data, 4, 32)