Repository: vyperlang/vyper

Issue:
Source map only available for runtime bytecode
### Version Information

* vyper Version (output of `vyper --version`): `latest`

### What's your issue about?

Vyper `source_map` argument currently only provides the runtime contract source map by default. In order to fully support debuggers, it would be helpful to have source maps also for deployable bytecode, just like Solidity.

### How can it be fixed?

-


Discussion:
charles-cooper: thanks!

Patch:
diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py
index bb2cfa34..226a7624 100755
--- a/vyper/cli/vyper_compile.py
+++ b/vyper/cli/vyper_compile.py
@@ -24,7 +24,8 @@ bytecode_runtime   - Bytecode at runtime
 blueprint_bytecode - Deployment bytecode for an ERC-5202 compatible blueprint
 abi                - ABI in JSON format
 abi_python         - ABI in python format
-source_map         - Vyper source map
+source_map         - Vyper source map of deployable bytecode
+source_map_runtime - Vyper source map of runtime bytecode
 method_identifiers - Dictionary of method signature to method identifier
 userdoc            - Natspec user documentation
 devdoc             - Natspec developer documentation
@@ -52,6 +53,7 @@ combined_json_outputs = [
     "abi",
     "layout",
     "source_map",
+    "source_map_runtime",
     "method_identifiers",
     "userdoc",
     "devdoc",
diff --git a/vyper/cli/vyper_json.py b/vyper/cli/vyper_json.py
index 42b017fb..4f737326 100755
--- a/vyper/cli/vyper_json.py
+++ b/vyper/cli/vyper_json.py
@@ -22,10 +22,10 @@ TRANSLATE_MAP = {
     "evm.methodIdentifiers": "method_identifiers",
     "evm.bytecode.object": "bytecode",
     "evm.bytecode.opcodes": "opcodes",
+    "evm.bytecode.sourceMap": "source_map",
     "evm.deployedBytecode.object": "bytecode_runtime",
     "evm.deployedBytecode.opcodes": "opcodes_runtime",
-    "evm.deployedBytecode.sourceMap": "source_map",
-    "evm.deployedBytecode.sourceMapFull": "source_map_full",
+    "evm.deployedBytecode.sourceMap": "source_map_runtime",
     "interface": "interface",
     "ir": "ir_dict",
     "ir_runtime": "ir_runtime_dict",
@@ -341,24 +341,24 @@ def format_to_output_dict(compiler_data: dict) -> dict:
             output_contracts["evm"] = {"methodIdentifiers": data["method_identifiers"]}
 
         evm_keys = ("bytecode", "opcodes")
-        if any(i in data for i in evm_keys):
+        pc_maps_keys = ("source_map",)
+        if any(i in data for i in evm_keys + pc_maps_keys):
             evm = output_contracts.setdefault("evm", {}).setdefault("bytecode", {})
             if "bytecode" in data:
                 evm["object"] = data["bytecode"]
             if "opcodes" in data:
                 evm["opcodes"] = data["opcodes"]
+            if "source_map" in data:
+                evm["sourceMap"] = data["source_map"]
 
-        pc_maps_keys = ("source_map", "source_map_full")
-        if any(i + "_runtime" in data for i in evm_keys) or any(i in data for i in pc_maps_keys):
+        if any(i + "_runtime" in data for i in evm_keys + pc_maps_keys):
             evm = output_contracts.setdefault("evm", {}).setdefault("deployedBytecode", {})
             if "bytecode_runtime" in data:
                 evm["object"] = data["bytecode_runtime"]
             if "opcodes_runtime" in data:
                 evm["opcodes"] = data["opcodes_runtime"]
-            if "source_map" in data:
-                evm["sourceMap"] = data["source_map"]["pc_pos_map_compressed"]
-            if "source_map_full" in data:
-                evm["sourceMapFull"] = data["source_map_full"]
+            if "source_map_runtime" in data:
+                evm["sourceMap"] = data["source_map_runtime"]
 
     return output_dict
 
diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py
index 26439d29..9ed38885 100644
--- a/vyper/compiler/__init__.py
+++ b/vyper/compiler/__init__.py
@@ -37,7 +37,7 @@ OUTPUT_FORMATS = {
     "abi": output.build_abi_output,
     "asm": output.build_asm_output,
     "source_map": output.build_source_map_output,
-    "source_map_full": output.build_source_map_output,
+    "source_map_runtime": output.build_source_map_runtime_output,
     # requires bytecode
     "bytecode": output.build_bytecode_output,
     "bytecode_runtime": output.build_bytecode_runtime_output,
diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py
index c9b138ba..577afd38 100644
--- a/vyper/compiler/output.py
+++ b/vyper/compiler/output.py
@@ -288,16 +288,13 @@ def _build_node_identifier(ast_node):
     return (ast_node.module_node.source_id, ast_node.node_id)
 
 
-def build_source_map_output(compiler_data: CompilerData) -> dict:
+def _build_source_map_output(compiler_data, bytecode, pc_maps):
     """
     Generate source map output in various formats. Note that integrations
     are encouraged to use pc_ast_map since the information it provides is
     a superset of the other formats, and the other types are included
     for legacy reasons.
     """
-    bytecode, pc_maps = compile_ir.assembly_to_evm(
-        compiler_data.assembly_runtime, insert_compiler_metadata=False
-    )
     # sort the pc maps alphabetically
     # CMC 2024-03-09 is this really necessary?
     out = {}
@@ -322,6 +319,20 @@ def build_source_map_output(compiler_data: CompilerData) -> dict:
     return out
 
 
+def build_source_map_output(compiler_data: CompilerData) -> dict:
+    bytecode, pc_maps = compile_ir.assembly_to_evm(
+        compiler_data.assembly, insert_compiler_metadata=False
+    )
+    return _build_source_map_output(compiler_data, bytecode, pc_maps)
+
+
+def build_source_map_runtime_output(compiler_data: CompilerData) -> dict:
+    bytecode, pc_maps = compile_ir.assembly_to_evm(
+        compiler_data.assembly_runtime, insert_compiler_metadata=False
+    )
+    return _build_source_map_output(compiler_data, bytecode, pc_maps)
+
+
 # generate a solidity-style source map. this functionality is deprecated
 # in favor of pc_ast_map, and may not be maintained to the same level
 # as pc_ast_map.


Test patch:
diff --git a/tests/unit/cli/vyper_compile/test_compile_files.py b/tests/unit/cli/vyper_compile/test_compile_files.py
index 6467ff6d..3856aa33 100644
--- a/tests/unit/cli/vyper_compile/test_compile_files.py
+++ b/tests/unit/cli/vyper_compile/test_compile_files.py
@@ -22,6 +22,7 @@ def test_combined_json_keys(chdir_tmp_path, make_file):
         "blueprint_bytecode",
         "abi",
         "source_map",
+        "source_map_runtime",
         "layout",
         "method_identifiers",
         "userdoc",
diff --git a/tests/unit/cli/vyper_json/test_compile_json.py b/tests/unit/cli/vyper_json/test_compile_json.py
index f4c93c08..ef3284cd 100644
--- a/tests/unit/cli/vyper_json/test_compile_json.py
+++ b/tests/unit/cli/vyper_json/test_compile_json.py
@@ -171,12 +171,15 @@ def test_compile_json(input_json, input_bundle):
             "userdoc": data["userdoc"],
             "metadata": data["metadata"],
             "evm": {
-                "bytecode": {"object": data["bytecode"], "opcodes": data["opcodes"]},
+                "bytecode": {
+                    "object": data["bytecode"],
+                    "opcodes": data["opcodes"],
+                    "sourceMap": data["source_map"],
+                },
                 "deployedBytecode": {
                     "object": data["bytecode_runtime"],
                     "opcodes": data["opcodes_runtime"],
-                    "sourceMap": data["source_map"]["pc_pos_map_compressed"],
-                    "sourceMapFull": data["source_map_full"],
+                    "sourceMap": data["source_map_runtime"],
                 },
                 "methodIdentifiers": data["method_identifiers"],
             },
@@ -266,7 +269,7 @@ def test_exc_handler_to_dict_compiler(input_json):
 
 
 def test_source_ids_increment(input_json):
-    input_json["settings"]["outputSelection"] = {"*": ["ast", "evm.deployedBytecode.sourceMapFull"]}
+    input_json["settings"]["outputSelection"] = {"*": ["ast", "evm.deployedBytecode.sourceMap"]}
     result = compile_json(input_json)
 
     def get(filename, contractname):
@@ -275,7 +278,7 @@ def test_source_ids_increment(input_json):
 
         # grab it via source map to sanity check
         contract_info = result["contracts"][filename][contractname]["evm"]
-        pc_ast_map = contract_info["deployedBytecode"]["sourceMapFull"]["pc_ast_map"]
+        pc_ast_map = contract_info["deployedBytecode"]["sourceMap"]["pc_ast_map"]
         pc_item = next(iter(pc_ast_map.values()))
         source_id, node_id = pc_item
         assert ret == source_id