diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 03dfd1e9e90f..18dbb87d20f0 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -161,6 +161,10 @@ set(sources interface/SMTSolverCommand.h interface/StandardCompiler.cpp interface/StandardCompiler.h + interface/StandardJSONInput.h + interface/StandardJSONInput.cpp + interface/StandardJSONOutput.h + interface/StandardJSONOutput.cpp interface/StorageLayout.cpp interface/StorageLayout.h interface/UniversalCallback.h diff --git a/libsolidity/interface/StandardJSONInput.cpp b/libsolidity/interface/StandardJSONInput.cpp new file mode 100644 index 000000000000..e2e9c67526f0 --- /dev/null +++ b/libsolidity/interface/StandardJSONInput.cpp @@ -0,0 +1,131 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#include + +#include +#include + +#include +#include +#include +#include + +#include +using namespace solidity::frontend; +using namespace solidity::frontend::json; +using namespace solidity::frontend::json::input; + +void input::to_json(Json& _json, YulOptimizerDetails const& _details) +{ + if (_details.stackAllocation) + _json["stackAllocation"] = *_details.stackAllocation; + if (_details.optimizerSteps) + _json["optimizerSteps"] = *_details.optimizerSteps; +} + +void input::to_json(Json& _json, OptimizerDetails const& _details) +{ + if (_details.peephole) + _json["peephole"] = *_details.peephole; + if (_details.orderLiterals) + _json["orderLiterals"] = *_details.orderLiterals; + if (_details.inliner) + _json["inliner"] = *_details.inliner; + if (_details.jumpdestRemover) + _json["jumpdestRemover"] = *_details.jumpdestRemover; + if (_details.deduplicate) + _json["deduplicate"] = *_details.deduplicate; + if (_details.cse) + _json["cse"] = *_details.cse; + if (_details.constantOptimizer) + _json["constantOptimizer"] = *_details.constantOptimizer; + if (_details.simpleCounterForLoopUncheckedIncrement) + _json["simpleCounterForLoopUncheckedIncrement"] = *_details.simpleCounterForLoopUncheckedIncrement; + if (_details.yul) + _json["yul"] = *_details.yul; + if (_details.yulDetails && _details.yul && *_details.yul) + _json["yulDetails"] = *_details.yulDetails; +} + +void input::to_json(Json& _json, Optimizer const& _optimizer) +{ + if (_optimizer.enable) + _json["enabled"] = *_optimizer.enable; + if (_optimizer.runs) + _json["runs"] = *_optimizer.runs; + if (_optimizer.details) + _json["details"] = *_optimizer.details; +} + +void input::to_json(Json& _json, Debug const& _debug) +{ + if (_debug.revertStrings) + _json["revertStrings"] = revertStringsToString(_debug.revertStrings.value()); +} + +void input::to_json(Json& _json, Metadata const& _metadata) +{ + if (_metadata.appendCBOR) + { + _json["appendCBOR"] = *_metadata.appendCBOR; + if (*_metadata.appendCBOR && _metadata.bytecodeHash) + _json["bytecodeHash"] = metadataHashToString(_metadata.bytecodeHash.value()); + } +} + +void input::to_json(Json& _json, Settings const& _settings) +{ + if (_settings.optimizer) + _json["optimizer"] = *_settings.optimizer; + if (_settings.evmVersion) + _json["evmVersion"] = (*_settings.evmVersion).name(); + if (_settings.eofVersion) + _json["eofVersion"] = *_settings.eofVersion; + if (_settings.viaIR) + _json["viaIR"] = *_settings.viaIR; + if (_settings.viaSSACFG) + _json["viaSSACFG"] = *_settings.viaSSACFG; + if (_settings.debug) + _json["debug"] = *_settings.debug; + if (_settings.metadata) + _json["metadata"] = *_settings.metadata; +} + +void json::to_json(Json& _json, StandardJSONInput const& _input) +{ + _json["language"] = "Solidity"; + + for (auto& [name, source] : _input.sources) + _json["sources"][name]["content"] = source; + + if (_input.settings) + { + _json["settings"] = *_input.settings; + for (auto& [name, source] : _input.sources) + { + _json["settings"]["libraries"][name] = _input.libraries | ranges::views::transform([](auto const& entry) { + auto const& [libraryName, address] = entry; + auto parts = libraryName | ranges::views::split(':') | ranges::to>(); + return std::pair{parts.back(), "0x" + address.hex()}; + }) | ranges::to(); + } + _json["settings"]["outputSelection"]["*"]["*"] = Json::array({"*"}); + _json["settings"]["outputSelection"]["*"][""] = Json::array({"ast"}); + } +} + diff --git a/libsolidity/interface/StandardJSONInput.h b/libsolidity/interface/StandardJSONInput.h new file mode 100644 index 000000000000..a4c339124697 --- /dev/null +++ b/libsolidity/interface/StandardJSONInput.h @@ -0,0 +1,158 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace solidity; +using namespace solidity::util; + +namespace solidity::frontend::json +{ + +namespace input +{ + struct YulOptimizerDetails + { + /// Improve allocation of stack slots for variables, can free up stack slots early. + /// Default: true if Yul optimizer is enabled. + std::optional stackAllocation; + /// Optimization step sequence. The general form of the value is "
:". + /// If it does not contain the ':' delimiter, it is interpreted as the main + /// sequence and the default is used for the cleanup sequence. + /// Default: true. + std::optional optimizerSteps; + }; + + struct OptimizerDetails + { + // Peephole optimizer (opcode-based). Default: true. + std::optional peephole = std::nullopt; + // Inliner (opcode-based). Optional. Default: true when optimization is enabled. + std::optional inliner = std::nullopt; + // Unused JUMPDEST remover (opcode-based). Default: true. + std::optional jumpdestRemover = std::nullopt; + // Literal reordering (codegen-based). Default: true when optimization is enabled. + std::optional orderLiterals = std::nullopt; + // Block deduplicator (opcode-based). Default: true when optimization is enabled. + std::optional deduplicate = std::nullopt; + // Common subexpression elimination (opcode-based). Default: true when optimization is enabled. + std::optional cse = std::nullopt; + // Constant optimizer (opcode-based). Default: true when optimization is enabled. + std::optional constantOptimizer = std::nullopt; + // Unchecked loop increment (codegen-based). Default: true. + std::optional simpleCounterForLoopUncheckedIncrement = std::nullopt; + // Yul optimizer. Default: true when optimization is enabled. + std::optional yul = std::nullopt; + /// Tuning options for the Yul optimizer. + std::optional yulDetails; + }; + + struct Optimizer + { + /// Turn on the optimizer. Default: false. + std::optional enable; + /// Optimize for how many times you intend to run the code. Default: 200. + std::optional runs; + /// State of all optimizer components. + std::optional details; + }; + + struct Debug + { + /// How to treat revert (and require) reason strings. Default: `RevertStrings::Default`. + std::optional revertStrings = std::nullopt; + }; + + struct Metadata + { + /// The CBOR metadata is appended at the end of the bytecode by default. Default: true. + /// Setting this to false omits the metadata from the runtime and deploy time code + std::optional appendCBOR = std::nullopt; + /// Use the given hash method for the metadata hash that is appended to the bytecode. Default: `MetadataHash::IPFS`. + std::optional bytecodeHash = std::nullopt; + }; + + struct Settings + { + /// Experimental mode toggle. Default: false. + std::optional experimental = std::nullopt; + /// The optimizer settings. + std::optional optimizer = std::nullopt; + /// Version of the EVM to compile for. + std::optional evmVersion = std::nullopt; + /// EVM Object Format version to compile for (experimental). + std::optional eofVersion = std::nullopt; + /// Change compilation pipeline to go through the Yul intermediate representation. Default: false. + std::optional viaIR = std::nullopt; + /// Turn on SSA CFG-based code generation via the IR (experimental, implies viaIR: true). Default: true; + std::optional viaSSACFG = std::nullopt; + /// Debugging settings. + std::optional debug = std::nullopt; + /// Metadata settings. + std::optional metadata = std::nullopt; + }; + + /// + void to_json(Json&, YulOptimizerDetails const&); + void to_json(Json&, OptimizerDetails const&); + void to_json(Json&, Optimizer const&); + void to_json(Json&, Debug const&); + void to_json(Json&, Metadata const&); + void to_json(Json&, Settings const&); +} + +/** + * The input the compiler is requested to compile with. It carries source and + * compiler configuration. + */ +struct StandardJSONInput +{ + /// Source code which should be be compiled. + std::map sources = {}; + /// Information on which library is deployed where. + std::map libraries = {}; + /// Contract name without a colon prefix. + std::optional contractName = std::nullopt; + /// The compiler settings, e.g. EVM version, optimizer settings and Yul config. + std::optional settings = std::nullopt; +}; + +/// +void to_json(Json&, StandardJSONInput const&); + +} diff --git a/libsolidity/interface/StandardJSONOutput.cpp b/libsolidity/interface/StandardJSONOutput.cpp new file mode 100644 index 000000000000..5dab3cc6e9d5 --- /dev/null +++ b/libsolidity/interface/StandardJSONOutput.cpp @@ -0,0 +1,168 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +using namespace solidity::frontend; +using namespace solidity::frontend::json; +using namespace solidity::frontend::json::output; + +ABI const& Contract::abi() const +{ + if (!m_abi) + m_abi = m_raw.at("abi"); + return *m_abi; +} + +std::string const& Contract::metadata() const +{ + if (!m_metadata) + m_metadata = m_raw.at("metadata"); + return *m_metadata; +} + +EVM const& Contract::evm() const +{ + if (!m_evm) + m_evm = m_raw.at("evm"); + return *m_evm; +} + +void output::from_json(Json const& _json, SourceLocation& _sourceLocation) +{ + _sourceLocation = SourceLocation{ + _json["file"], + _json["start"], + _json["end"], + _json.contains("message") ? std::optional{_json.at("message")} : std::nullopt + }; +} + +void output::from_json(Json const& _json, Error& _error) +{ + if (_json.contains("sourceLocation")) + _error.sourceLocation = _json.at("sourceLocation"); + if (_json.contains("secondarySourceLocations") && _json.at("secondarySourceLocations").is_array()) + _error.secondarySourceLocations = _json.at("secondarySourceLocations"); + if (_json.contains("errorCode")) + _error.errorCode = langutil::ErrorId{std::stoull(_json.at("errorCode").get())}; + + auto type = langutil::Error::parseErrorType(_json.at("type")); + solAssert(type); + + _error.type = type.value(); + _error.message = _json.at("message"); +} + +void output::from_json(Json const& _json, Source& _source) +{ + _source.id = _json.at("id"); + if (_json.contains("ast")) + _source.ast = _json.at("ast"); +} + +void output::from_json(Json const& _json, ABIParameter& _param) +{ + _param.name = _json.at("name").get(); + _param.type = _json.at("type").get(); + _param.internalType = _json.contains("internalType") ? + std::optional{_json.at("internalType")} : + std::nullopt; + _param.indexed = _json.contains("indexed") ? + std::optional{_json.at("indexed").get()} : + std::nullopt; + _param.components = _json.contains("components") ? + std::optional{_json.at("components").get>()} : + std::nullopt; +} + +void output::from_json(Json const& _json, ABIConstructor& _constructor) +{ + _constructor.stateMutability = stateMutabilityFromString(_json.at("stateMutability")); + _constructor.inputs = _json.at("inputs").get>(); +} + + +void output::from_json(Json const& _json, ABIFunction& _function) +{ + _function.name = _json.at("name"); + _function.stateMutability = stateMutabilityFromString(_json.at("stateMutability"));; + _function.inputs = _json.at("inputs").get>(); + _function.outputs = _json.at("outputs").get>(); +} + +void output::from_json(Json const& _json, ABIEvent& _event) +{ + _event.name = _json.at("name"); + _event.isAnonymous = _json.at("anonymous"); + _event.inputs = _json.at("inputs").get>(); +} + +void output::from_json(Json const& _json, ABIError& _event) +{ + _event.name = _json.at("name"); + _event.inputs = _json.at("inputs").get>(); +} + +void output::from_json(Json const& _json, ABIEntry& _entry) +{ + auto const type = _json.at("type").get(); + if (type == "constructor") + _entry = _json.get(); + else if (type == "function") + _entry = _json.get(); + else if (type == "event") + _entry = _json.get(); + else if (type == "error") + _entry = _json.get(); +} + +void output::from_json(Json const& _json, ByteOffset& _byteOffset) +{ + _byteOffset.start = _json.at("start").get(); + _byteOffset.length = _json.at("length").get(); +} + +void output::from_json(Json const& _json, Bytecode& _bytecode) +{ + _bytecode.object = util::fromHex(_json.at("object").get()); + _bytecode.linkReferences = _json.at("linkReferences"); +} + +void output::from_json(Json const& _json, EVM& _evm) +{ + _evm.bytecode = _json.at("bytecode").get(); + _evm.methodIdentifiers = _json.at("methodIdentifiers"); +} + +void output::from_json(Json const& _json, Contracts& _contracts) +{ + for (auto const& [source, contractsJson] : _json.items()) + for (auto const& [name, contractJson] : contractsJson.items()) + _contracts[source].push_back(output::Contract{name, contractJson}); +} diff --git a/libsolidity/interface/StandardJSONOutput.h b/libsolidity/interface/StandardJSONOutput.h new file mode 100644 index 000000000000..9884fdfc0c7e --- /dev/null +++ b/libsolidity/interface/StandardJSONOutput.h @@ -0,0 +1,318 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace solidity; +using namespace solidity::util; + +namespace solidity::frontend::json +{ + +namespace output +{ + struct SourceLocation + { + /// The name of the source file. + std::string file; + /// The start of the source position. + int start; + /// The end of the source position. + int end; + /// If this is a secondary source location, a message should exist. + std::optional message; + + /// @returns this source location converted to the compiler's internal type. + langutil::SourceLocation toInternalSourceLocation() const + { + return langutil::SourceLocation{start, end, std::make_shared(file)}; + } + }; + + struct Error + { + /// Location within the source file. + std::optional sourceLocation; + /// Further locations (e.g. places of conflicting declarations). + std::optional> secondarySourceLocations; + /// Unique code for the cause of the error. + std::optional errorCode; + /// The error type. + langutil::Error::Type type; + /// The error message. + std::optional message; + + /// @returns this error converted to the compiler's internal type. + langutil::Error toInternalError() const + { + auto locations = secondarySourceLocations.value_or(std::vector{}); + return { + errorCode.value_or({}), + type, + message.value_or({}), + sourceLocation.value_or({}).toInternalSourceLocation(), + langutil::SecondarySourceLocation{ + locations | ranges::views::filter([](auto const& s) { + return s.message.has_value(); + }) | ranges::views::transform([](auto const& s) { + return std::pair{s.message.value(), s.toInternalSourceLocation()}; + }) | ranges::to() + } + }; + } + }; + + struct Source + { + /// Identifier of the source (used in source maps) + size_t id; + /// The AST object + std::optional ast; + }; + + struct ABIParameter + { + /// The ABI-level type name, e.g. "address", "uint256" + std::string name; + /// The ABI-level type, e.g. "address", "uint256", "tuple" + std::string type; + /// The Solidity-level type, may differ for e.g. enums or user-defined value types. + std::optional internalType; + /// Whether this parameter is indexed. Only present for event inputs. + std::optional indexed; + /// Component parameters, only present when type == "tuple" + std::optional> components; + }; + + struct ABIConstructor + { + /// The constrcutor's input parameters. + std::vector inputs; + /// The state mutability of the constructor: "pure", "view", "nonpayable", or "payable". + StateMutability stateMutability; + }; + + struct ABIFunction + { + /// The name of the function. + std::string name; + /// The function's input parameters. + std::vector inputs; + /// The function's output parameters. + std::vector outputs; + /// The state mutability of the function: "pure", "view", "nonpayable", or "payable". + StateMutability stateMutability; + }; + + struct ABIEvent + { + /// The name of the event. + std::string name; + /// The event's parameters. + std::vector inputs; + /// Whether the event is anonymous. Anonymous events do not have their + /// signature included in the topic list. + bool isAnonymous; + }; + + struct ABIError + { + /// The name of the error. + std::string name; + /// The error's parameters. + std::vector inputs; + }; + + struct ByteOffset + { + /// The start of the bytes to replace. + size_t start; + /// The length of the bytes to replace. + size_t length; + }; + + using LinkReferences = std::map>; + + struct Bytecode + { + /// The bytecode as a hex. + bytes object; + /// The byte offsets per source and library. If not empty, this is an unlinked object. + std::map linkReferences; + }; + + struct EVM + { + /// The bytecode as a hex. + Bytecode bytecode; + /// The list of function hashes. + std::map methodIdentifiers; + }; + + /// A single entry in the contract ABI, either an event or a function. + using ABIEntry = std::variant; + using ABI = std::vector; + + /** + * Represents a compiled contract. Carries the contract-specific compiler output. + */ + class Contract + { + public: + /// Creates with a name and the raw JSON object representing this contract. + explicit Contract(std::string const& _name, Json _raw): + m_name(_name), + m_raw(_raw) + {} + + /// @returns the contract name. + std::string const& name() const + { + return m_name; + } + + /// @returns a reference to the raw JSON object representing this contract. + Json const& raw() const + { + return m_raw; + } + + /// @returns the contract ABI definitions. + ABI const& abi() const; + + /// @returns the metadata matching the pipeline selected using the viaIR setting. + std::string const& metadata() const; + + /// @returns the EVM-related outputs + EVM const& evm() const; + + protected: + std::string m_name; + Json m_raw; + + private: + mutable std::optional m_abi; + mutable std::optional m_metadata; + mutable std::optional m_evm; + }; + + using Errors = std::vector; + using Sources = std::map; + using Contracts = std::map>; + + /// Enables JSON deserialization for standard output types. + /// Supports parsing via `nlohmann::json`'s ADL pattern. + void from_json(Json const&, SourceLocation&); + void from_json(Json const&, Error&); + void from_json(Json const&, Source&); + void from_json(Json const&, ABIParameter&); + void from_json(Json const&, ABIConstructor&); + void from_json(Json const&, ABIFunction&); + void from_json(Json const&, ABIEvent&); + void from_json(Json const&, ABIError&); + void from_json(Json const&, ABIEntry&); + void from_json(Json const&, ByteOffset&); + void from_json(Json const&, Bytecode&); + void from_json(Json const&, EVM&); + void from_json(Json const&, Contracts&); +} + +/** + * Output generated by the compiler during the last compilation run. It stores + * the raw JSON output and provides accessors that deserialize lazily. + */ +class StandardJSONOutput +{ +public: + /// Creates a standard output object that takes ownership of the JSON output given. + StandardJSONOutput(Json _raw): + m_raw(std::move(_raw)) + {} + + /// Noncopyable, but has move semantics. + StandardJSONOutput(const StandardJSONOutput&) = delete; + StandardJSONOutput& operator=(const StandardJSONOutput&) = delete; + StandardJSONOutput(StandardJSONOutput&&) noexcept = default; + StandardJSONOutput& operator=(StandardJSONOutput&&) noexcept = default; + + ~StandardJSONOutput() = default; + + /// @retusn the raw JSON output produced by the compiler. + Json const& raw() const + { + return m_raw; + } + + /// @returns all errors, warnings, infos that may have occurred during compilation. + output::Errors const& errors() const + { + if (!m_errors) + m_errors = m_raw.contains("errors") ? m_raw.at("errors") : output::Errors{}; + return *m_errors; + } + + /// @returns the file-level outputs. Can be limited / filtered by the `outputSelection` input. + output::Sources const& sources() const + { + if (!m_sources) + m_sources = m_raw.contains("sources") ? m_raw.at("sources") : output::Sources{}; + return *m_sources; + } + + + /// @returns a map of all sources with their contracts. + output::Contracts const& contracts() const + { + if (!m_contracts) + m_contracts = m_raw.contains("contracts") ? m_raw.at("contracts") : output::Contracts{}; + return *m_contracts; + } + +private: + Json m_raw; + mutable std::optional m_errors; + mutable std::optional m_sources; + mutable std::optional m_contracts; +}; + +} diff --git a/test/libsolidity/interface/StandardJSON.cpp b/test/libsolidity/interface/StandardJSON.cpp new file mode 100644 index 000000000000..95f1fc54c29d --- /dev/null +++ b/test/libsolidity/interface/StandardJSON.cpp @@ -0,0 +1,58 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include +#include + +#include +#include +#include + +using namespace solidity::util; +using namespace solidity::test; + +#define TEST_CASE_NAME (boost::unit_test::framework::current_test_case().p_name) + +namespace solidity::frontend::test +{ + +BOOST_AUTO_TEST_SUITE(StandardJSONTests) + +BOOST_AUTO_TEST_CASE(default_creation) +{ + json::StandardJSONInput input; + + BOOST_CHECK_EQUAL(input.settings.has_value(), false); +} + +BOOST_AUTO_TEST_CASE(default_to_json) +{ + using namespace solidity::frontend::json; + + Json json{StandardJSONInput{}}; + + BOOST_CHECK_EQUAL(json["language"], "Solidity"); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace solidity::frontend::test +