Repository: vyperlang/vyper

Issue:
Compiler panic when importing interfaces with `max_value()` as a default argument value
### Version Information

* vyper Version (output of `vyper --version`): 0.3.6
* OS: osx
* Python Version (output of `python --version`): 3.9.8

### What's your issue about?

Please include information like:

* full output of the error you received
* what command you ran
* the code that caused the failure (see [this link](https://help.github.com/articles/basic-writing-and-formatting-syntax/) for help with formatting code)
* please try running your example with the `--verbose` flag turned on

When a contract has a function that uses `max_value(uint256)` as a default argument value, it causes a compiler panic when it is imported as an interface:

```vyper
# ContractB.vy (compiles successfully)

@view
@external
def foo(_max: uint256 = max_value(uint256)) -> uint256:
    return _max

```

```vyper
# ContractA.vy (fails to compile)

import ContractB as ContractB
```

Compiling ContractA results in a compile error:
```console
$ vyper ContractA.vy
Error compiling: ContractA.vy
vyper.exceptions.CompilerPanic: max_value should always be folded

This is an unhandled internal compiler error. Please create an issue on Github to notify the developers.
```

### How can it be fixed?

Fill this in if you know how to fix it.



Discussion:
The same failure occurs when the default value is `min_value(uint256)`, but it succeeds with `empty(uint256)`. 
As a workaround, if I declare a constant for the default value, it works, but I have to declare the same constant in the contract doing the import:

```vyper
# ContractB.vy (compiles successfully)

# `MAX_UINT256` is deprecated, so we use `_MAX_UINT256`
_MAX_UINT256: constant(uint256) = max_value(uint256)


@view
@external
def foo(_max: uint256 = _MAX_UINT256) -> uint256:
    return _max
```

Fails unless `_MAX_UINT256` is declared in ContractA:
```vyper
# ContractA.vy (fails to compile)

import ContractB as ContractB
```
```console
$ vyper ContractA.vy
Error compiling: ContractA.vy
vyper.exceptions.UndeclaredDefinition: '_MAX_UINT256' has not been declared.
  contract "ContractB", function "foo", line 9:24
       8 @external
  ---> 9 def foo(_max: uint256 = _MAX_UINT256) -> uint256:
  -------------------------------^
      10     return _max

```
Succeeds:
```vyper
# ContractA.vy (compiles successfully)

import ContractB as ContractB

_MAX_UINT256: constant(uint256) = max_value(uint256)
```

```console
$ vyper ContractA.vy
0x61000d61000f60003961000d6000f3a165767970657283000306000b
```
whoa, that also seems like a bug. you should not be able to override constants in the imported contract from the importing contract.
Also works if I declare the interface inline:

```vyper
# ContractA.vy (compiles successfully)

interface ContractB:
    def foo(_max: uint256 = max_value(uint256)) -> uint256: view
```
Related to the need to redeclare constants locally, but probably a separate issue -- if the imported contract references a struct in its function signatures, I also have to declare that Struct in the importing contract. 

(I know this was reported in https://github.com/vyperlang/vyper/issues/2670)

Resolving patch:
diff --git a/vyper/builtin_functions/functions.py b/vyper/builtin_functions/functions.py
--- a/vyper/builtin_functions/functions.py
+++ b/vyper/builtin_functions/functions.py
@@ -125,17 +125,30 @@
 class FoldedFunction(BuiltinFunction):
     # Base class for nodes which should always be folded
 
-    def fetch_call_return(self, node):  # pragma: no cover
-        raise CompilerPanic(f"{self._id} should always be folded")
+    # Since foldable builtin functions are not folded before semantics validation,
+    # this flag is used for `check_kwargable` in semantics validation.
+    _kwargable = True
+
 
-    def infer_arg_types(self, node):  # pragma: no cover
-        raise CompilerPanic(f"{self._id} should always be folded")
+class TypenameFoldedFunction(FoldedFunction):
+    # Base class for builtin functions that:
+    # (1) take a typename as the only argument; and
+    # (2) should always be folded.
 
-    def infer_kwarg_types(self, node):  # pragma: no cover
-        raise CompilerPanic(f"{self._id} should always be folded")
+    # "TYPE_DEFINITION" is a placeholder value for a type definition string, and
+    # will be replaced by a `TypeTypeDefinition` object in `infer_arg_types`.
+    _inputs = [("typename", "TYPE_DEFINITION")]
 
-    def build_IR(self, *args, **kwargs):  # pragma: no cover
-        raise CompilerPanic(f"{self._id} should always be folded")
+    def fetch_call_return(self, node):
+        type_ = self.infer_arg_types(node)[0].typedef
+        return type_
+
+    def infer_arg_types(self, node):
+        validate_call_args(node, 1)
+        input_typedef = TypeTypeDefinition(
+            get_type_from_annotation(node.args[0], DataLocation.MEMORY)
+        )
+        return [input_typedef]
 
 
 class Floor(BuiltinFunction):
@@ -731,24 +744,32 @@ def evaluate(self, node):
         if " " in args[0].value:
             raise InvalidLiteral("Invalid function signature - no spaces allowed.")
 
+        return_type = self.infer_kwarg_types(node)
+        value = abi_method_id(args[0].value)
+
+        if isinstance(return_type, Bytes4Definition):
+            return vy_ast.Hex.from_node(node, value=hex(value))
+        else:
+            return vy_ast.Bytes.from_node(node, value=value.to_bytes(4, "big"))
+
+    def fetch_call_return(self, node):
+        validate_call_args(node, 1, ["output_type"])
+
+        type_ = self.infer_kwarg_types(node)
+        return type_
+
+    def infer_kwarg_types(self, node):
         if node.keywords:
             return_type = get_type_from_annotation(node.keywords[0].value, DataLocation.UNSET)
             if isinstance(return_type, Bytes4Definition):
-                is_bytes4 = True
+                return Bytes4Definition()
             elif isinstance(return_type, BytesArrayDefinition) and return_type.length == 4:
-                is_bytes4 = False
+                return BytesArrayDefinition(4)
             else:
                 raise ArgumentException("output_type must be Bytes[4] or bytes4", node.keywords[0])
-        else:
-            # If `output_type` is not given, default to `Bytes[4]`
-            is_bytes4 = False
 
-        value = abi_method_id(args[0].value)
-
-        if is_bytes4:
-            return vy_ast.Hex.from_node(node, value=hex(value))
-        else:
-            return vy_ast.Bytes.from_node(node, value=value.to_bytes(4, "big"))
+        # If `output_type` is not given, default to `Bytes[4]`
+        return BytesArrayDefinition(4)
 
 
 class ECRecover(BuiltinFunction):
@@ -877,7 +898,7 @@ class Extract32(BuiltinFunction):
     _inputs = [("b", BytesArrayPrimitive()), ("start", UnsignedIntegerAbstractType())]
     # "TYPE_DEFINITION" is a placeholder value for a type definition string, and
     # will be replaced by a `TypeTypeDefinition` object in `infer_kwarg_types`
-    # (note that it is ignored in validate_args)
+    # (note that it is ignored in `_validate_arg_types`)
     _kwargs = {"output_type": KwargSettings("TYPE_DEFINITION", "bytes32")}
     _return_type = None
 
@@ -2264,28 +2285,9 @@ def build_IR(self, expr, args, kwargs, context):
         )
 
 
-class Empty(BuiltinFunction):
+class Empty(TypenameFoldedFunction):
 
     _id = "empty"
-    # "TYPE_DEFINITION" is a placeholder value for a type definition string, and
-    # will be replaced by a `TypeTypeDefinition` object in `infer_arg_types`
-    # (note that it is ignored in `validate_args`)
-    _inputs = [("typename", "TYPE_DEFINITION")]
-
-    # Since `empty` is not folded before semantics validation, this flag is used
-    # for `check_kwargable` in semantics validation.
-    _kwargable = True
-
-    def fetch_call_return(self, node):
-        type_ = self.infer_arg_types(node)[0].typedef
-        return type_
-
-    def infer_arg_types(self, node):
-        validate_call_args(node, 1)
-        input_typedef = TypeTypeDefinition(
-            get_type_from_annotation(node.args[0], DataLocation.MEMORY)
-        )
-        return [input_typedef]
 
     @process_inputs
     def build_IR(self, expr, args, kwargs, context):
@@ -2599,9 +2601,7 @@ def build_IR(self, expr, args, kwargs, context):
             )
 
 
-class _MinMaxValue(FoldedFunction):
-    _inputs = [("typename", "TYPE_DEFINITION")]
-
+class _MinMaxValue(TypenameFoldedFunction):
     def evaluate(self, node):
         self._validate_arg_types(node)
         input_type = get_type_from_annotation(node.args[0], DataLocation.UNSET)
@@ -2642,9 +2642,7 @@ def _eval_decimal(self, type_):
         return typinfo.decimal_bounds[1]
 
 
-class Epsilon(FoldedFunction):
-
-    _inputs = [("typename", "TYPE_DEFINITION")]
+class Epsilon(TypenameFoldedFunction):
     _id = "epsilon"
 
     def evaluate(self, node):


Test patch:
diff --git a/tests/parser/functions/test_interfaces.py b/tests/parser/functions/test_interfaces.py
--- a/tests/parser/functions/test_interfaces.py
+++ b/tests/parser/functions/test_interfaces.py
@@ -269,6 +269,43 @@ def test():
     assert erc20.balanceOf(sender) == 1000
 
 
+@pytest.mark.parametrize(
+    "kwarg,typ,expected",
+    [
+        ("max_value(uint256)", "uint256", 2 ** 256 - 1),
+        ("min_value(int128)", "int128", -(2 ** 127)),
+        ("empty(uint8[2])", "uint8[2]", [0, 0]),
+        ('method_id("vyper()", output_type=bytes4)', "bytes4", b"\x82\xcbE\xfb"),
+        ("epsilon(decimal)", "decimal", Decimal("1E-10")),
+    ],
+)
+def test_external_call_to_interface_kwarg(get_contract, kwarg, typ, expected):
+    code_a = f"""
+@external
+@view
+def foo(_max: {typ} = {kwarg}) -> {typ}:
+    return _max
+    """
+
+    code_b = f"""
+import one as ContractA
+
+@external
+@view
+def bar(a_address: address) -> {typ}:
+    return ContractA(a_address).foo()
+    """
+
+    contract_a = get_contract(code_a)
+    contract_b = get_contract(
+        code_b,
+        *[contract_a.address],
+        interface_codes={"ContractA": {"type": "vyper", "code": code_a}},
+    )
+
+    assert contract_b.bar(contract_a.address) == expected
+
+
 def test_external_call_to_builtin_interface(w3, get_contract):
     token_code = """
 balanceOf: public(HashMap[address, uint256])