Repository: vyperlang/vyper

Issue:
'metadata' output selection is not supported with standard JSON
### Version Information

* vyper Version (output of `vyper --version`): 0.3.9
* OS: osx
* Python Version (output of `python --version`): 3.11.4

### What's your issue about?

The function metadata can be requested with `vyper -f metadata`, but not with standard JSON.
If I add 'metadata' to the standard JSON output selection, I see the following error:
```
[{"message":"Invalid outputSelection - 'metadata'"}]
```

### How can it be fixed?

Add the `metadata` output selection to standard JSON.


Discussion:
charles-cooper: @tserg can we add a test for this in the vyper json tests?

Patch:
diff --git a/docs/built-in-functions.rst b/docs/built-in-functions.rst
index bfaa8fdd..45cf9ec8 100644
--- a/docs/built-in-functions.rst
+++ b/docs/built-in-functions.rst
@@ -184,13 +184,14 @@ Vyper has three built-ins for contract creation; all three contract creation bui
     The implementation of ``create_copy_of`` assumes that the code at ``target`` is smaller than 16MB. While this is much larger than the EIP-170 constraint of 24KB, it is a conservative size limit intended to future-proof deployer contracts in case the EIP-170 constraint is lifted. If the code at ``target`` is larger than 16MB, the behavior of ``create_copy_of`` is undefined.
 
 
-.. py:function:: create_from_blueprint(target: address, *args, value: uint256 = 0, code_offset=0, [, salt: bytes32]) -> address
+.. py:function:: create_from_blueprint(target: address, *args, value: uint256 = 0, raw_args: bool = False, code_offset: int = 0, [, salt: bytes32]) -> address
 
     Copy the code of ``target`` into memory and execute it as initcode. In other words, this operation interprets the code at ``target`` not as regular runtime code, but directly as initcode. The ``*args`` are interpreted as constructor arguments, and are ABI-encoded and included when executing the initcode.
 
     * ``target``: Address of the blueprint to invoke
     * ``*args``: Constructor arguments to forward to the initcode.
     * ``value``: The wei value to send to the new contract address (Optional, default 0)
+    * ``raw_args``: If ``True``, ``*args`` must be a single ``Bytes[...]`` argument, which will be interpreted as a raw bytes buffer to forward to the create operation (which is useful for instance, if pre- ABI-encoded data is passed in from elsewhere). (Optional, default ``False``)
     * ``code_offset``: The offset to start the ``EXTCODECOPY`` from (Optional, default 0)
     * ``salt``: A ``bytes32`` value utilized by the deterministic ``CREATE2`` opcode (Optional, if not supplied, ``CREATE`` is used)
 
@@ -201,7 +202,7 @@ Vyper has three built-ins for contract creation; all three contract creation bui
         @external
         def foo(blueprint: address) -> address:
             arg1: uint256 = 18
-            arg2: String = "some string"
+            arg2: String[32] = "some string"
             return create_from_blueprint(blueprint, arg1, arg2, code_offset=1)
 
 .. note::
@@ -226,7 +227,7 @@ Vyper has three built-ins for contract creation; all three contract creation bui
     * ``to``: Destination address to call to
     * ``data``: Data to send to the destination address
     * ``max_outsize``: Maximum length of the bytes array returned from the call. If the returned call data exceeds this length, only this number of bytes is returned. (Optional, default ``0``)
-    * ``gas``: The amount of gas to attach to the call. If not set, all remaining gas is forwarded.
+    * ``gas``: The amount of gas to attach to the call. (Optional, defaults to ``msg.gas``).
     * ``value``: The wei value to send to the address (Optional, default ``0``)
     * ``is_delegate_call``: If ``True``, the call will be sent as ``DELEGATECALL`` (Optional, default ``False``)
     * ``is_static_call``: If ``True``, the call will be sent as ``STATICCALL`` (Optional, default ``False``)
@@ -264,6 +265,10 @@ Vyper has three built-ins for contract creation; all three contract creation bui
             assert success
             return response
 
+    .. note::
+
+        Regarding "forwarding all gas", note that, while Vyper will provide ``msg.gas`` to the call, in practice, there are some subtleties around forwarding all remaining gas on the EVM which are out of scope of this documentation and could be subject to change. For instance, see the language in EIP-150 around "all but one 64th".
+
 .. py:function:: raw_log(topics: bytes32[4], data: Union[Bytes, bytes32]) -> None
 
     Provides low level access to the ``LOG`` opcodes, emitting a log without having to specify an ABI type.
@@ -500,7 +505,7 @@ Data Manipulation
 
     * ``b``: ``Bytes`` list to extract from
     * ``start``: Start point to extract from
-    * ``output_type``: Type of output (``bytes32``, ``integer``, or ``address``). Defaults to ``bytes32``.
+    * ``output_type``: Type of output (``bytesM``, ``integer``, or ``address``). Defaults to ``bytes32``.
 
     Returns a value of the type specified by ``output_type``.
 
diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py
index 95759372..f0720283 100644
--- a/vyper/builtins/functions.py
+++ b/vyper/builtins/functions.py
@@ -29,7 +29,6 @@ from vyper.codegen.core import (
     get_type_for_exact_size,
     ir_tuple_from_args,
     make_setter,
-    needs_external_call_wrap,
     promote_signed_int,
     sar,
     shl,
@@ -1418,7 +1417,7 @@ class BitwiseNot(BuiltinFunction):
 
     def evaluate(self, node):
         if not self.__class__._warned:
-            vyper_warn("`bitwise_not()` is deprecated! Please use the ^ operator instead.")
+            vyper_warn("`bitwise_not()` is deprecated! Please use the ~ operator instead.")
             self.__class__._warned = True
 
         validate_call_args(node, 1)
@@ -1917,9 +1916,8 @@ class CreateFromBlueprint(_CreateBase):
 
                 # copy the target code into memory.
                 # layout starting from mem_ofst:
-                # 00...00 (22 0's) | preamble | bytecode
+                # <target initcode> | <abi-encoded args OR arg buffer if raw_arg=True>
                 ir.append(["extcodecopy", target, mem_ofst, code_offset, codesize])
-
                 ir.append(copy_bytes(add_ofst(mem_ofst, codesize), argbuf, encoded_args_len, bufsz))
 
                 # theoretically, dst = "msize", but just be safe.
@@ -2368,8 +2366,6 @@ class Print(BuiltinFunction):
 class ABIEncode(BuiltinFunction):
     _id = "_abi_encode"  # TODO prettier to rename this to abi.encode
     # signature: *, ensure_tuple=<literal_bool> -> Bytes[<calculated len>]
-    # (check the signature manually since we have no utility methods
-    # to handle varargs.)
     # explanation of ensure_tuple:
     # default is to force even a single value into a tuple,
     # e.g. _abi_encode(bytes) -> _abi_encode((bytes,))
@@ -2494,6 +2490,8 @@ class ABIDecode(BuiltinFunction):
         return output_type.typedef
 
     def infer_arg_types(self, node):
+        self._validate_arg_types(node)
+
         validate_call_args(node, 2, ["unwrap_tuple"])
 
         data_type = get_exact_type_from_node(node.args[0])
@@ -2530,24 +2528,11 @@ class ABIDecode(BuiltinFunction):
             )
 
         data = ensure_in_memory(data, context)
+
         with data.cache_when_complex("to_decode") as (b1, data):
             data_ptr = bytes_data_ptr(data)
             data_len = get_bytearray_length(data)
 
-            # Normally, ABI-encoded data assumes the argument is a tuple
-            # (See comments for `wrap_value_for_external_return`)
-            # However, we do not want to use `wrap_value_for_external_return`
-            # technique as used in external call codegen because in order to be
-            # type-safe we would need an extra memory copy. To avoid a copy,
-            # we manually add the ABI-dynamic offset so that it is
-            # re-interpreted in-place.
-            if (
-                unwrap_tuple is True
-                and needs_external_call_wrap(output_typ)
-                and output_typ.abi_type.is_dynamic()
-            ):
-                data_ptr = add_ofst(data_ptr, 32)
-
             ret = ["seq"]
 
             if abi_min_size == abi_size_bound:
@@ -2556,18 +2541,30 @@ class ABIDecode(BuiltinFunction):
                 # runtime assert: abi_min_size <= data_len <= abi_size_bound
                 ret.append(clamp2(abi_min_size, data_len, abi_size_bound, signed=False))
 
-            # return pointer to the buffer
-            ret.append(data_ptr)
-
-            return b1.resolve(
-                IRnode.from_list(
-                    ret,
-                    typ=output_typ,
-                    location=data.location,
-                    encoding=Encoding.ABI,
-                    annotation=f"abi_decode({output_typ})",
-                )
+            to_decode = IRnode.from_list(
+                data_ptr,
+                typ=wrapped_typ,
+                location=data.location,
+                encoding=Encoding.ABI,
+                annotation=f"abi_decode({output_typ})",
             )
+            to_decode.encoding = Encoding.ABI
+
+            # TODO optimization: skip make_setter when we don't need
+            # input validation
+
+            output_buf = context.new_internal_variable(wrapped_typ)
+            output = IRnode.from_list(output_buf, typ=wrapped_typ, location=MEMORY)
+
+            # sanity check buffer size for wrapped output type will not buffer overflow
+            assert wrapped_typ.memory_bytes_required == output_typ.memory_bytes_required
+            ret.append(make_setter(output, to_decode))
+
+            ret.append(output)
+            # finalize. set the type and location for the return buffer.
+            # (note: unwraps the tuple type if necessary)
+            ret = IRnode.from_list(ret, typ=output_typ, location=MEMORY)
+            return b1.resolve(ret)
 
 
 class _MinMaxValue(TypenameFoldedFunction):
@@ -2586,7 +2583,6 @@ class _MinMaxValue(TypenameFoldedFunction):
         if isinstance(input_type, IntegerT):
             ret = vy_ast.Int.from_node(node, value=val)
 
-        # TODO: to change to known_type once #3213 is merged
         ret._metadata["type"] = input_type
         return ret
 
diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py
index 4a1c9155..f6d82c3f 100755
--- a/vyper/cli/vyper_json.py
+++ b/vyper/cli/vyper_json.py
@@ -29,7 +29,7 @@ TRANSLATE_MAP = {
     "interface": "interface",
     "ir": "ir_dict",
     "ir_runtime": "ir_runtime_dict",
-    # "metadata": "metadata",  # don't include  in "*" output for now
+    "metadata": "metadata",
     "layout": "layout",
     "userdoc": "userdoc",
 }
diff --git a/vyper/codegen/function_definitions/external_function.py b/vyper/codegen/function_definitions/external_function.py
index 32236e9a..65276469 100644
--- a/vyper/codegen/function_definitions/external_function.py
+++ b/vyper/codegen/function_definitions/external_function.py
@@ -135,20 +135,17 @@ def _generate_kwarg_handlers(
     return ret
 
 
-# TODO it would be nice if this returned a data structure which were
-# amenable to generating a jump table instead of the linear search for
-# method_id we have now.
 def generate_ir_for_external_function(code, func_t, context):
     # TODO type hints:
     # def generate_ir_for_external_function(
     #    code: vy_ast.FunctionDef,
     #    func_t: ContractFunctionT,
     #    context: Context,
-    #    check_nonpayable: bool,
     # ) -> IRnode:
-    """Return the IR for an external function. Includes code to inspect the method_id,
-    enter the function (nonpayable and reentrancy checks), handle kwargs and exit
-    the function (clean up reentrancy storage variables)
+    """
+    Return the IR for an external function. Returns IR for the body
+    of the function, handle kwargs and exit the function. Also returns
+    metadata required for `module.py` to construct the selector table.
     """
     nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_t)
 
diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py
index 334c5ba6..9ef492c3 100644
--- a/vyper/compiler/output.py
+++ b/vyper/compiler/output.py
@@ -104,7 +104,6 @@ def build_ir_runtime_dict_output(compiler_data: CompilerData) -> dict:
 
 
 def build_metadata_output(compiler_data: CompilerData) -> dict:
-    warnings.warn("metadata output format is unstable!")
     sigs = compiler_data.function_signatures
 
     def _var_rec_dict(variable_record):
diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py
index c10df3b8..b391b339 100644
--- a/vyper/semantics/analysis/local.py
+++ b/vyper/semantics/analysis/local.py
@@ -150,7 +150,7 @@ def _validate_msg_data_attribute(node: vy_ast.Attribute) -> None:
         allowed_builtins = ("slice", "len", "raw_call")
         if not isinstance(parent, vy_ast.Call) or parent.get("func.id") not in allowed_builtins:
             raise StructureException(
-                "msg.data is only allowed inside of the slice or len functions", node
+                "msg.data is only allowed inside of the slice, len or raw_call functions", node
             )
         if parent.get("func.id") == "slice":
             ok_args = len(parent.args) == 3 and isinstance(parent.args[2], vy_ast.Int)
diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py
index 02ae82fa..e5942229 100644
--- a/vyper/semantics/analysis/module.py
+++ b/vyper/semantics/analysis/module.py
@@ -98,7 +98,6 @@ class ModuleAnalyzer(VyperNodeVisitorBase):
         _ns.update({k: namespace[k] for k in namespace._scopes[-1]})  # type: ignore
         module_node._metadata["namespace"] = _ns
 
-        # check for collisions between 4byte function selectors
         self_members = namespace["self"].typ.members
 
         # get list of internal function calls made by each function


Test patch:
diff --git a/tests/cli/vyper_json/test_output_selection.py b/tests/cli/vyper_json/test_output_selection.py
index c72f06f5..3b12e2b5 100644
--- a/tests/cli/vyper_json/test_output_selection.py
+++ b/tests/cli/vyper_json/test_output_selection.py
@@ -52,3 +52,9 @@ def test_solc_style():
     input_json = {"settings": {"outputSelection": {"foo.vy": {"": ["abi"], "foo.vy": ["ir"]}}}}
     sources = {"foo.vy": ""}
     assert get_input_dict_output_formats(input_json, sources) == {"foo.vy": ["abi", "ir_dict"]}
+
+
+def test_metadata():
+    input_json = {"settings": {"outputSelection": {"*": ["metadata"]}}}
+    sources = {"foo.vy": ""}
+    assert get_input_dict_output_formats(input_json, sources) == {"foo.vy": ["metadata"]}
diff --git a/tests/parser/functions/test_abi_decode.py b/tests/parser/functions/test_abi_decode.py
index 2f9b9305..2216a5bd 100644
--- a/tests/parser/functions/test_abi_decode.py
+++ b/tests/parser/functions/test_abi_decode.py
@@ -344,6 +344,34 @@ def abi_decode(x: Bytes[96]) -> (uint256, uint256):
     assert_tx_failed(lambda: c.abi_decode(input_))
 
 
+def test_clamper_nested_uint8(get_contract, assert_tx_failed):
+    # check that _abi_decode clamps on word-types even when it is in a nested expression
+    # decode -> validate uint8 -> revert if input >= 256 -> cast back to uint256
+    contract = """
+@external
+def abi_decode(x: uint256) -> uint256:
+    a: uint256 = convert(_abi_decode(slice(msg.data, 4, 32), (uint8)), uint256)
+    return a
+    """
+    c = get_contract(contract)
+    assert c.abi_decode(255) == 255
+    assert_tx_failed(lambda: c.abi_decode(256))
+
+
+def test_clamper_nested_bytes(get_contract, assert_tx_failed):
+    # check that _abi_decode clamps dynamic even when it is in a nested expression
+    # decode -> validate Bytes[20] -> revert if len(input) > 20 -> convert back to -> add 1
+    contract = """
+@external
+def abi_decode(x: Bytes[96]) -> Bytes[21]:
+    a: Bytes[21] = concat(b"a", _abi_decode(x, Bytes[20]))
+    return a
+    """
+    c = get_contract(contract)
+    assert c.abi_decode(abi.encode("(bytes)", (b"bc",))) == b"abc"
+    assert_tx_failed(lambda: c.abi_decode(abi.encode("(bytes)", (b"a" * 22,))))
+
+
 @pytest.mark.parametrize(
     "output_typ,input_",
     [
diff --git a/tests/parser/syntax/test_abi_decode.py b/tests/parser/syntax/test_abi_decode.py
new file mode 100644
index 00000000..f05ff429
--- /dev/null
+++ b/tests/parser/syntax/test_abi_decode.py
@@ -0,0 +1,45 @@
+import pytest
+
+from vyper import compiler
+from vyper.exceptions import TypeMismatch
+
+fail_list = [
+    (
+        """
+@external
+def foo(j: uint256) -> bool:
+    s: bool = _abi_decode(j, bool, unwrap_tuple= False)
+    return s
+    """,
+        TypeMismatch,
+    ),
+    (
+        """
+@external
+def bar(j: String[32]) -> bool:
+    s: bool = _abi_decode(j, bool, unwrap_tuple= False)
+    return s
+    """,
+        TypeMismatch,
+    ),
+]
+
+
+@pytest.mark.parametrize("bad_code,exc", fail_list)
+def test_abi_encode_fail(bad_code, exc):
+    with pytest.raises(exc):
+        compiler.compile_code(bad_code)
+
+
+valid_list = [
+    """
+@external
+def foo(x: Bytes[32]) -> uint256:
+    return _abi_decode(x, uint256)
+    """
+]
+
+
+@pytest.mark.parametrize("good_code", valid_list)
+def test_abi_encode_success(good_code):
+    assert compiler.compile_code(good_code) is not None