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:
codecov-commenter: # [Codecov](https://codecov.io/gh/vyperlang/vyper/pull/2632?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) Report
> Merging [#2632](https://codecov.io/gh/vyperlang/vyper/pull/2632?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) (7d2db27) into [master](https://codecov.io/gh/vyperlang/vyper/commit/82f44eea2c7be79c6db805c1aa873fabbe0118b7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) (82f44ee) will **decrease** coverage by `34.26%`.
> The diff coverage is `33.33%`.

[![Impacted file tree graph](https://codecov.io/gh/vyperlang/vyper/pull/2632/graphs/tree.svg?width=650&height=150&src=pr&token=fTg5Co13HV&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang)](https://codecov.io/gh/vyperlang/vyper/pull/2632?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang)

```diff
@@             Coverage Diff             @@
##           master    #2632       +/-   ##
===========================================
- Coverage   86.55%   52.28%   -34.27%     
===========================================
  Files          92       92               
  Lines        9460     9454        -6     
  Branches     2400     2368       -32     
===========================================
- Hits         8188     4943     -3245     
- Misses        781     3922     +3141     
- Partials      491      589       +98     
```


| [Impacted Files](https://codecov.io/gh/vyperlang/vyper/pull/2632?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) | Coverage Δ | |
|---|---|---|
| [vyper/codegen/expr.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvY29kZWdlbi9leHByLnB5) | `37.25% <0.00%> (-43.44%)` | :arrow_down: |
| [vyper/semantics/validation/local.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvc2VtYW50aWNzL3ZhbGlkYXRpb24vbG9jYWwucHk=) | `40.57% <100.00%> (-50.91%)` | :arrow_down: |
| [vyper/builtin\_interfaces/ERC20Detailed.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvYnVpbHRpbl9pbnRlcmZhY2VzL0VSQzIwRGV0YWlsZWQucHk=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [vyper/lll/s\_expressions.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvbGxsL3NfZXhwcmVzc2lvbnMucHk=) | `5.88% <0.00%> (-88.24%)` | :arrow_down: |
| [vyper/ast/natspec.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvYXN0L25hdHNwZWMucHk=) | `12.34% <0.00%> (-86.42%)` | :arrow_down: |
| [vyper/codegen/external\_call.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvY29kZWdlbi9leHRlcm5hbF9jYWxsLnB5) | `12.50% <0.00%> (-82.15%)` | :arrow_down: |
| [vyper/cli/vyper\_json.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvY2xpL3Z5cGVyX2pzb24ucHk=) | `10.23% <0.00%> (-75.20%)` | :arrow_down: |
| [vyper/cli/utils.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvY2xpL3V0aWxzLnB5) | `18.42% <0.00%> (-73.69%)` | :arrow_down: |
| [vyper/codegen/function\_definitions/utils.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvY29kZWdlbi9mdW5jdGlvbl9kZWZpbml0aW9ucy91dGlscy5weQ==) | `28.57% <0.00%> (-71.43%)` | :arrow_down: |
| [vyper/ast/expansion.py](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvYXN0L2V4cGFuc2lvbi5weQ==) | `23.52% <0.00%> (-70.59%)` | :arrow_down: |
| ... and [60 more](https://codecov.io/gh/vyperlang/vyper/pull/2632/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/vyperlang/vyper/pull/2632?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/vyperlang/vyper/pull/2632?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang). Last update [82f44ee...7d2db27](https://codecov.io/gh/vyperlang/vyper/pull/2632?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).


Patch:
diff --git a/vyper/ast/folding.py b/vyper/ast/folding.py
index 3d8f8ebd..f1e84f50 100644
--- a/vyper/ast/folding.py
+++ b/vyper/ast/folding.py
@@ -4,6 +4,8 @@ from typing import Union
 from vyper.ast import nodes as vy_ast
 from vyper.builtin_functions import DISPATCH_TABLE
 from vyper.exceptions import UnfoldableNode
+from vyper.semantics.types.bases import BaseTypeDefinition, DataLocation
+from vyper.semantics.types.utils import get_type_from_annotation
 
 BUILTIN_CONSTANTS = {
     "EMPTY_BYTES32": (
@@ -172,7 +174,17 @@ def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int:
             # annotation is not wrapped in `constant(...)`
             continue
 
-        changed_nodes += replace_constant(vyper_module, node.target.id, node.value, False)
+        # Extract type definition from propagated annotation
+        constant_annotation = node.get("annotation.args")[0]
+        type_ = (
+            get_type_from_annotation(constant_annotation, DataLocation.UNSET)
+            if constant_annotation
+            else None
+        )
+
+        changed_nodes += replace_constant(
+            vyper_module, node.target.id, node.value, False, type_=type_
+        )
 
     return changed_nodes
 
@@ -180,12 +192,19 @@ def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int:
 # TODO constant folding on log events
 
 
-def _replace(old_node, new_node):
+def _replace(old_node, new_node, type_=None):
     if isinstance(new_node, vy_ast.Constant):
-        return new_node.from_node(old_node, value=new_node.value)
+        new_node = new_node.from_node(old_node, value=new_node.value)
+        if type_:
+            new_node._metadata["type"] = type_
+        return new_node
     elif isinstance(new_node, vy_ast.List):
-        list_values = [_replace(old_node, i) for i in new_node.elements]
-        return new_node.from_node(old_node, elements=list_values)
+        base_type = type_.value_type if type_ else None
+        list_values = [_replace(old_node, i, type_=base_type) for i in new_node.elements]
+        new_node = new_node.from_node(old_node, elements=list_values)
+        if type_:
+            new_node._metadata["type"] = type_
+        return new_node
     else:
         raise UnfoldableNode
 
@@ -195,6 +214,7 @@ def replace_constant(
     id_: str,
     replacement_node: Union[vy_ast.Constant, vy_ast.List],
     raise_on_error: bool,
+    type_: BaseTypeDefinition = None,
 ) -> int:
     """
     Replace references to a variable name with a literal value.
@@ -209,6 +229,8 @@ def replace_constant(
         Vyper ast node representing the literal value to be substituted in.
     raise_on_error: bool
         Boolean indicating if `UnfoldableNode` exception should be raised or ignored.
+    type_ : BaseTypeDefinition, optional
+        Type definition to be propagated to type checker.
 
     Returns
     -------
@@ -236,7 +258,7 @@ def replace_constant(
                 continue
 
         try:
-            new_node = _replace(node, replacement_node)
+            new_node = _replace(node, replacement_node, type_=type_)
         except UnfoldableNode:
             if raise_on_error:
                 raise
diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py
index 5e1e3674..8868c1f4 100644
--- a/vyper/codegen/expr.py
+++ b/vyper/codegen/expr.py
@@ -430,9 +430,9 @@ class Expr:
             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
index 3bda158f..76e8544b 100644
--- a/vyper/semantics/validation/local.py
+++ b/vyper/semantics/validation/local.py
@@ -25,7 +25,7 @@ from vyper.semantics.environment import CONSTANT_ENVIRONMENT_VARS, MUTABLE_ENVIR
 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,
@@ -36,7 +36,6 @@ from vyper.semantics.types.utils import get_type_from_annotation
 from vyper.semantics.types.value.address import AddressDefinition
 from vyper.semantics.types.value.array_value import StringDefinition
 from vyper.semantics.types.value.boolean import BoolDefinition
-from vyper.semantics.types.value.numeric import Uint256Definition
 from vyper.semantics.validation.annotation import StatementAnnotationVisitor
 from vyper.semantics.validation.base import VyperNodeVisitorBase
 from vyper.semantics.validation.utils import (
@@ -166,14 +165,6 @@ class FunctionNodeVisitor(VyperNodeVisitorBase):
         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,
@@ -323,7 +314,7 @@ class FunctionNodeVisitor(VyperNodeVisitorBase):
                     raise StateAccessViolation("Value must be a literal", node)
                 if args[0].value <= 0:
                     raise StructureException("For loop must have at least 1 iteration", args[0])
-                validate_expected_type(args[0], Uint256Definition())
+                validate_expected_type(args[0], IntegerAbstractType())
                 type_list = get_possible_types_from_node(args[0])
             else:
                 validate_expected_type(args[0], IntegerAbstractType())
diff --git a/vyper/semantics/validation/utils.py b/vyper/semantics/validation/utils.py
index 7aa0633a..70eb1e5d 100644
--- a/vyper/semantics/validation/utils.py
+++ b/vyper/semantics/validation/utils.py
@@ -88,6 +88,8 @@ class _ExprTypeChecker:
     def get_possible_types_from_node(self, node, only_definitions=True):
         """
         Find all possible types for a given node.
+        If the node's metadata contains type information propagated from constant folding,
+        then that type is returned.
 
         Arguments
         ---------
@@ -102,6 +104,10 @@ class _ExprTypeChecker:
         List
             A list of type objects
         """
+        # Early termination if typedef is propagated in metadata
+        if "type" in node._metadata:
+            return [node._metadata["type"]]
+
         fn = self._find_fn(node)
         types_list = fn(node)
         if only_definitions:
@@ -113,6 +119,7 @@ class _ExprTypeChecker:
                     )
                 else:
                     raise InvalidReference("Expected a literal or variable", node)
+
         if all(isinstance(i, IntegerAbstractType) for i in types_list):
             # for numeric types, sort according by number of bits descending
             # we do this to ensure literals are cast with the largest possible type


Test patch:
diff --git a/tests/parser/features/decorators/test_private.py b/tests/parser/features/decorators/test_private.py
index 74924e72..c2932587 100644
--- 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
index d19277c1..b8450155 100644
--- 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
index e540f106..a4a062ef 100644
--- a/tests/parser/syntax/test_msg_data.py
+++ b/tests/parser/syntax/test_msg_data.py
@@ -2,7 +2,7 @@ import pytest
 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)
diff --git a/tests/parser/types/numbers/test_constants.py b/tests/parser/types/numbers/test_constants.py
index 7669040c..04a6f03e 100644
--- a/tests/parser/types/numbers/test_constants.py
+++ b/tests/parser/types/numbers/test_constants.py
@@ -1,6 +1,10 @@
+import itertools
 from decimal import Decimal
 
+import pytest
+
 from vyper.compiler import compile_code
+from vyper.exceptions import InvalidType
 
 
 def test_builtin_constants(get_contract_with_gas_estimation):
@@ -127,6 +131,22 @@ def test_add(a: uint256) -> uint256:
     assert c.test_add(7) == 40
 
 
+# Would be nice to put this somewhere accessible, like in vyper.types or something
+integer_types = ["uint8", "int128", "int256", "uint256"]
+
+
+@pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2))
+def test_custom_constants_fail(get_contract, assert_compile_failed, storage_type, return_type):
+    code = f"""
+MY_CONSTANT: constant({storage_type}) = 1
+
+@external
+def foo() -> {return_type}:
+    return MY_CONSTANT
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidType)
+
+
 def test_constant_address(get_contract):
     code = """
 OWNER: constant(address) = 0x0000000000000000000000000000000000000012
diff --git a/tests/parser/types/test_dynamic_array.py b/tests/parser/types/test_dynamic_array.py
index 128fca59..00160914 100644
--- a/tests/parser/types/test_dynamic_array.py
+++ b/tests/parser/types/test_dynamic_array.py
@@ -1,6 +1,8 @@
+import itertools
+
 import pytest
 
-from vyper.exceptions import ArrayIndexException, OverflowException
+from vyper.exceptions import ArrayIndexException, InvalidType, OverflowException, TypeMismatch
 
 
 def test_list_tester_code(get_contract_with_gas_estimation):
@@ -481,3 +483,42 @@ def ix(i: uint256) -> decimal:
 
 
 # TODO test loops
+
+# Would be nice to put this somewhere accessible, like in vyper.types or something
+integer_types = ["uint8", "int128", "int256", "uint256"]
+
+
+@pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2))
+def test_constant_list_fail(get_contract, assert_compile_failed, storage_type, return_type):
+    code = f"""
+MY_CONSTANT: constant(DynArray[{storage_type}, 3]) = [1, 2, 3]
+
+@external
+def foo() -> DynArray[{return_type}, 3]:
+    return MY_CONSTANT
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidType)
+
+
+@pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2))
+def test_constant_list_fail_2(get_contract, assert_compile_failed, storage_type, return_type):
+    code = f"""
+MY_CONSTANT: constant(DynArray[{storage_type}, 3]) = [1, 2, 3]
+
+@external
+def foo() -> {return_type}:
+    return MY_CONSTANT[0]
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidType)
+
+
+@pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2))
+def test_constant_list_fail_3(get_contract, assert_compile_failed, storage_type, return_type):
+    code = f"""
+MY_CONSTANT: constant(DynArray[{storage_type}, 3]) = [1, 2, 3]
+
+@external
+def foo(i: uint256) -> {return_type}:
+    return MY_CONSTANT[i]
+    """
+    assert_compile_failed(lambda: get_contract(code), TypeMismatch)
diff --git a/tests/parser/types/test_lists.py b/tests/parser/types/test_lists.py
index 2f532cd3..fd4bbad0 100644
--- a/tests/parser/types/test_lists.py
+++ b/tests/parser/types/test_lists.py
@@ -1,6 +1,8 @@
+import itertools
+
 import pytest
 
-from vyper.exceptions import ArrayIndexException, OverflowException
+from vyper.exceptions import ArrayIndexException, InvalidType, OverflowException, TypeMismatch
 
 
 def test_list_tester_code(get_contract_with_gas_estimation):
@@ -430,6 +432,46 @@ def ix(i: uint256) -> {type}:
     assert_tx_failed(lambda: c.ix(len(value) + 1))
 
 
+# Would be nice to put this somewhere accessible, like in vyper.types or something
+integer_types = ["uint8", "int128", "int256", "uint256"]
+
+
+@pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2))
+def test_constant_list_fail(get_contract, assert_compile_failed, storage_type, return_type):
+    code = f"""
+MY_CONSTANT: constant({storage_type}[3]) = [1, 2, 3]
+
+@external
+def foo() -> {return_type}[3]:
+    return MY_CONSTANT
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidType)
+
+
+@pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2))
+def test_constant_list_fail_2(get_contract, assert_compile_failed, storage_type, return_type):
+    code = f"""
+MY_CONSTANT: constant({storage_type}[3]) = [1, 2, 3]
+
+@external
+def foo() -> {return_type}:
+    return MY_CONSTANT[0]
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidType)
+
+
+@pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2))
+def test_constant_list_fail_3(get_contract, assert_compile_failed, storage_type, return_type):
+    code = f"""
+MY_CONSTANT: constant({storage_type}[3]) = [1, 2, 3]
+
+@external
+def foo(i: uint256) -> {return_type}:
+    return MY_CONSTANT[i]
+    """
+    assert_compile_failed(lambda: get_contract(code), TypeMismatch)
+
+
 def test_constant_list_address(get_contract, assert_tx_failed):
     some_good_address = [
         "0x0000000000000000000000000000000000012345",
@@ -457,3 +499,63 @@ def ix(i: uint256) -> address:
         assert c.ix(i) == p
     # assert oob
     assert_tx_failed(lambda: c.ix(len(some_good_address) + 1))
+
+
+@pytest.mark.parametrize(
+    "type,value",
+    [
+        ("decimal", [[5.0, 11.0], [17.0, 29.0], [37.0, 41.0]]),
+        ("uint8", [[0, 1], [17, 250], [255, 2]]),
+        ("int128", [[0, -1], [1, -(2 ** 127)], [2 ** 127 - 1, -50]]),
+        ("int256", [[0, -1], [1, -(2 ** 255)], [2 ** 255 - 1, -50]]),
+        ("uint256", [[0, 1], [2 ** 8, 2 ** 255 + 1], [2 ** 256 - 1, 100]]),
+        (
+            "uint256",
+            [
+                [2 ** 255 + 1, 2 ** 255 + 2],
+                [2 ** 255 + 3, 2 ** 255 + 4],
+                [2 ** 255 + 5, 2 ** 255 + 6],
+            ],
+        ),
+        ("bool", [[True, False], [True, False], [True, False]]),
+    ],
+)
+def test_constant_nested_list(get_contract, assert_tx_failed, type, value):
+    code = f"""
+MY_LIST: constant({type}[{len(value[0])}][{len(value)}]) = {value}
+@external
+def ix(i: uint256, j: uint256) -> {type}:
+    return MY_LIST[i][j]
+    """
+    c = get_contract(code)
+    for i, p in enumerate(value):
+        for j, q in enumerate(p):
+            assert c.ix(i, j) == q
+    # assert oob
+    assert_tx_failed(lambda: c.ix(len(value) + 1, len(value[0]) + 1))
+
+
+@pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2))
+def test_constant_nested_list_fail(get_contract, assert_compile_failed, storage_type, return_type):
+    code = f"""
+MY_CONSTANT: constant({storage_type}[2][3]) = [[1, 2], [3, 4], [5, 6]]
+
+@external
+def foo() -> {return_type}[2][3]:
+    return MY_CONSTANT
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidType)
+
+
+@pytest.mark.parametrize("storage_type,return_type", itertools.permutations(integer_types, 2))
+def test_constant_nested_list_fail_2(
+    get_contract, assert_compile_failed, storage_type, return_type
+):
+    code = f"""
+MY_CONSTANT: constant({storage_type}[2][3]) = [[1, 2], [3, 4], [5, 6]]
+
+@external
+def foo() -> {return_type}:
+    return MY_CONSTANT[0][0]
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidType)