diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 19201545e524..4bf59b9f6dbf 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -1203,6 +1203,7 @@ LinkerObject const& Assembly::assembleLegacy() const size_t subTagSize = 1; std::map immutableReferencesBySub; + std::vector subAssemblies; for (auto const& sub: m_subs) { auto const& linkerObject = sub->assemble(); @@ -1215,6 +1216,7 @@ LinkerObject const& Assembly::assembleLegacy() const ); immutableReferencesBySub = linkerObject.immutableReferences; } + subAssemblies.push_back(linkerObject); for (size_t tagPos: sub->m_tagPositionsInBytecode) if (tagPos != std::numeric_limits::max() && numberEncodingSize(tagPos) > subTagSize) subTagSize = numberEncodingSize(tagPos); @@ -1465,6 +1467,16 @@ LinkerObject const& Assembly::assembleLegacy() const bytesRef r(ret.bytecode.data() + pos, bytesPerDataRef); toBigEndian(ret.bytecode.size(), r); } + + std::vector nestedSubAssemblies{}; + for (auto const& subAssembly: subAssemblies) + nestedSubAssemblies.insert(nestedSubAssemblies.end(), subAssembly.subAssemblyData.begin(), subAssembly.subAssemblyData.end()); + + size_t const currentBytecodeSize = ret.bytecode.size(); + updateSubAssemblyStartOffsets(nestedSubAssemblies, currentBytecodeSize); + + ret.subAssemblyData.push_back({0, ret.bytecode.size(), isCreation(), ret.toHex(), nestedSubAssemblies}); + return ret; } @@ -1776,3 +1788,12 @@ Assembly::OptimiserSettings Assembly::OptimiserSettings::translateSettings(front asmSettings.evmVersion = _evmVersion; return asmSettings; } + +void Assembly::updateSubAssemblyStartOffsets(std::vector& _subAssemblies, size_t const _currentBytecodeSize) const +{ + for (auto& subAssembly: _subAssemblies) + { + subAssembly.start = _currentBytecodeSize - subAssembly.length; + updateSubAssemblyStartOffsets(subAssembly.subs, _currentBytecodeSize); + } +} diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 92dfb0a44008..260f9d062ab3 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -287,6 +287,8 @@ class Assembly [[nodiscard]] bytes assemblePushDeployTimeAddress() const; [[nodiscard]] bytes assembleTag(AssemblyItem const& _item, size_t _pos, bool _addJumpDest) const; + void updateSubAssemblyStartOffsets(std::vector& _subAssemblies, size_t const _currentBytecodeSize) const; + protected: /// 0 is reserved for exception unsigned m_usedTags = 1; diff --git a/libevmasm/LinkerObject.h b/libevmasm/LinkerObject.h index c103a565a069..2219b8463a21 100644 --- a/libevmasm/LinkerObject.h +++ b/libevmasm/LinkerObject.h @@ -58,6 +58,16 @@ struct LinkerObject /// Bytecode offsets of named tags like function entry points. std::map functionDebugData; + struct SubAssembly { + size_t start; + size_t length; + bool isCreation; + std::string bytecode; + std::vector subs {}; + }; + + std::vector subAssemblyData; + /// Appends the bytecode of @a _other and incorporates its link references. void append(LinkerObject const& _other); diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 9357310c07e4..fcb7386869e0 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -237,7 +237,7 @@ bool isArtifactRequested(Json const& _outputSelection, std::string const& _file, std::vector evmObjectComponents(std::string const& _objectKind) { solAssert(_objectKind == "bytecode" || _objectKind == "deployedBytecode", ""); - std::vector components{"", ".object", ".opcodes", ".sourceMap", ".functionDebugData", ".generatedSources", ".linkReferences"}; + std::vector components{"", ".object", ".opcodes", ".sourceMap", ".functionDebugData", ".generatedSources", ".linkReferences", ".subAssemblyOffsets"}; if (_objectKind == "deployedBytecode") components.push_back(".immutableReferences"); return util::applyMap(components, [&](auto const& _s) { return "evm." + _objectKind + _s; }); @@ -374,6 +374,24 @@ Json formatImmutableReferences(std::map const& _subAssemblyOffsets) +{ + Json subs = Json::array(); + + for (auto const& subAssembly: _subAssemblyOffsets) + { + Json subAssemblyInfo; + subAssemblyInfo["start"] = Json::number_unsigned_t(subAssembly.start); + subAssemblyInfo["length"] = Json::number_unsigned_t(subAssembly.length); + subAssemblyInfo["isCreation"] = Json::boolean_t(subAssembly.isCreation); + if (!subAssembly.subs.empty()) + subAssemblyInfo["subs"] = formatSubAssemblyOffsets(subAssembly.subs); + subs.emplace_back(subAssemblyInfo); + } + + return subs; +} + std::optional checkKeys(Json const& _input, std::set const& _keys, std::string const& _name) { if (!_input.empty() && !_input.is_object()) @@ -1235,6 +1253,12 @@ Json StandardCompiler::importEVMAssembly(StandardCompiler::InputsAndSettings _in creationJSON["functionDebugData"] = formatFunctionDebugData(stack.object(sourceName).functionDebugData); if (evmCreationArtifactRequested("linkReferences")) creationJSON["linkReferences"] = formatLinkReferences(stack.object(sourceName).linkReferences); + if (evmCreationArtifactRequested("subAssemblyOffsets")) + { + Json ret = Json::object(); + ret["subs"] = formatSubAssemblyOffsets(stack.runtimeObject(sourceName).subAssemblyData); + creationJSON["subAssemblyOffsets"] = std::move(ret); + } evmData["bytecode"] = creationJSON; } @@ -1503,6 +1527,12 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu creationJSON["linkReferences"] = formatLinkReferences(compilerStack.object(contractName).linkReferences); if (evmCreationArtifactRequested("generatedSources")) creationJSON["generatedSources"] = compilerStack.generatedSources(contractName, /* _runtime */ false); + if (evmCreationArtifactRequested("subAssemblyOffsets")) + { + Json ret = Json::object(); + ret["subs"] = formatSubAssemblyOffsets(compilerStack.object(contractName).subAssemblyData); + creationJSON["subAssemblyOffsets"] = std::move(ret); + } evmData["bytecode"] = creationJSON; } @@ -1533,6 +1563,12 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu deployedJSON["immutableReferences"] = formatImmutableReferences(compilerStack.runtimeObject(contractName).immutableReferences); if (evmDeployedArtifactRequested("generatedSources")) deployedJSON["generatedSources"] = compilerStack.generatedSources(contractName, /* _runtime */ true); + if (evmDeployedArtifactRequested("subAssemblyOffsets")) + { + Json ret = Json::object(); + ret["subs"] = formatSubAssemblyOffsets(compilerStack.object(contractName).subAssemblyData); + deployedJSON["subAssemblyOffsets"] = std::move(ret); + } evmData["deployedBytecode"] = deployedJSON; } diff --git a/test/cmdlineTests/standard_import_asm_json/output.json b/test/cmdlineTests/standard_import_asm_json/output.json index 1db28796071a..716047755c65 100644 --- a/test/cmdlineTests/standard_import_asm_json/output.json +++ b/test/cmdlineTests/standard_import_asm_json/output.json @@ -11,7 +11,10 @@ "linkReferences": {}, "object": "", "opcodes":"", - "sourceMap":"" + "sourceMap":"", + "subAssemblyOffsets": { + "subs": [] + } }, "deployedBytecode": { "functionDebugData": {}, diff --git a/test/cmdlineTests/standard_import_asm_json_immutable_references/output.json b/test/cmdlineTests/standard_import_asm_json_immutable_references/output.json index f0554d8c602b..9acb66012e0f 100644 --- a/test/cmdlineTests/standard_import_asm_json_immutable_references/output.json +++ b/test/cmdlineTests/standard_import_asm_json_immutable_references/output.json @@ -8,7 +8,16 @@ "linkReferences": {}, "object": "", "opcodes":"", - "sourceMap":"" + "sourceMap":"", + "subAssemblyOffsets": { + "subs": [ + { + "isCreation": false, + "length": 87, + "start": 0 + } + ] + } }, "deployedBytecode": { "functionDebugData": {}, diff --git a/test/cmdlineTests/standard_import_asm_json_link_references/output.json b/test/cmdlineTests/standard_import_asm_json_link_references/output.json index fed476e620c5..5201286e76c5 100644 --- a/test/cmdlineTests/standard_import_asm_json_link_references/output.json +++ b/test/cmdlineTests/standard_import_asm_json_link_references/output.json @@ -8,7 +8,16 @@ "linkReferences": {}, "object": "", "opcodes":"", - "sourceMap": "" + "sourceMap": "", + "subAssemblyOffsets": { + "subs": [ + { + "isCreation": false, + "length": 75, + "start": 0 + } + ] + } }, "deployedBytecode": { "functionDebugData": {}, diff --git a/test/cmdlineTests/standard_import_ast_select_bytecode/output.json b/test/cmdlineTests/standard_import_ast_select_bytecode/output.json index c060efd971a7..10e6f48df5bf 100644 --- a/test/cmdlineTests/standard_import_ast_select_bytecode/output.json +++ b/test/cmdlineTests/standard_import_ast_select_bytecode/output.json @@ -9,7 +9,23 @@ "linkReferences": {}, "object": "", "opcodes":"", - "sourceMap":"" + "sourceMap":"", + "subAssemblyOffsets": { + "subs": [ + { + "isCreation": true, + "length": 130, + "start": 0, + "subs": [ + { + "isCreation": false, + "length": 104, + "start": 26 + } + ] + } + ] + } } } } diff --git a/test/cmdlineTests/standard_no_append_cbor/output.json b/test/cmdlineTests/standard_no_append_cbor/output.json index c060efd971a7..5a025b49448d 100644 --- a/test/cmdlineTests/standard_no_append_cbor/output.json +++ b/test/cmdlineTests/standard_no_append_cbor/output.json @@ -9,7 +9,23 @@ "linkReferences": {}, "object": "", "opcodes":"", - "sourceMap":"" + "sourceMap":"", + "subAssemblyOffsets": { + "subs": [ + { + "isCreation": true, + "length": 27, + "start": 0, + "subs": [ + { + "isCreation": false, + "length": 3, + "start": 24 + } + ] + } + ] + } } } } diff --git a/test/cmdlineTests/standard_subassembly_offsets/input.json b/test/cmdlineTests/standard_subassembly_offsets/input.json new file mode 100644 index 000000000000..b40e14bc0e04 --- /dev/null +++ b/test/cmdlineTests/standard_subassembly_offsets/input.json @@ -0,0 +1,19 @@ +{ + "language": "Solidity", + "sources": { + "contract.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.7.0 <0.9.0;\ncontract A {}\ncontract Storage { uint256 public number; bytes public code = type(A).creationCode; function store(uint256 num) public { number = num; }}" + } + }, + "settings": { + "evmVersion": "cancun", + "outputSelection": { + "*": { + "Storage": [ + "evm.bytecode.subAssemblyOffsets" + ] + } + } + } +} + diff --git a/test/cmdlineTests/standard_subassembly_offsets/output.json b/test/cmdlineTests/standard_subassembly_offsets/output.json new file mode 100644 index 000000000000..10e28fe7670a --- /dev/null +++ b/test/cmdlineTests/standard_subassembly_offsets/output.json @@ -0,0 +1,45 @@ +{ + "contracts": { + "contract.sol": { + "Storage": { + "evm": { + "bytecode": { + "subAssemblyOffsets": { + "subs": [ + { + "isCreation": true, + "length": 1787, + "start": 0, + "subs": [ + { + "isCreation": false, + "length": 780, + "start": 1007 + }, + { + "isCreation": true, + "length": 130, + "start": 1657, + "subs": [ + { + "isCreation": false, + "length": 104, + "start": 1683 + } + ] + } + ] + } + ] + } + } + } + } + } + }, + "sources": { + "contract.sol": { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json b/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json index 3869f7c652bd..8888e237a938 100644 --- a/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json +++ b/test/cmdlineTests/standard_undeployable_contract_all_outputs/output.json @@ -16,7 +16,10 @@ "linkReferences": {}, "object": "", "opcodes": "", - "sourceMap": "" + "sourceMap": "", + "subAssemblyOffsets": { + "subs": [] + } }, "deployedBytecode": { "functionDebugData": {}, @@ -25,7 +28,10 @@ "linkReferences": {}, "object": "", "opcodes": "", - "sourceMap": "" + "sourceMap": "", + "subAssemblyOffsets": { + "subs": [] + } }, "gasEstimates": null, "legacyAssembly": null, @@ -65,7 +71,10 @@ "linkReferences": {}, "object": "", "opcodes": "", - "sourceMap": "" + "sourceMap": "", + "subAssemblyOffsets": { + "subs": [] + } }, "deployedBytecode": { "functionDebugData": {}, @@ -74,7 +83,10 @@ "linkReferences": {}, "object": "", "opcodes": "", - "sourceMap": "" + "sourceMap": "", + "subAssemblyOffsets": { + "subs": [] + } }, "gasEstimates": null, "legacyAssembly": null,