Skip to content

Commit 62e27e4

Browse files
authored
Merge pull request #14285 from ethereum/evmjson-import-via-standard-json
Add support to import EVM Assembly JSON via Standard JSON.
2 parents ae7039f + 4ed2380 commit 62e27e4

File tree

29 files changed

+701
-13
lines changed

29 files changed

+701
-13
lines changed

Diff for: Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Compiler Features:
1313
* EVM: Support for the EVM Version "Cancun".
1414
* SMTChecker: Support `bytes.concat` except when string literals are passed as arguments.
1515
* TypeChecker: Comparison of internal function pointers now yields a warning, as it can produce unexpected results with the legacy pipeline enabled.
16+
* Standard JSON Interface: Add experimental support to import EVM assembly in the format used by ``--asm-json``.
1617

1718

1819
Bugfixes:

Diff for: docs/using-the-compiler.rst

+27-8
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ Input Description
208208
.. code-block:: javascript
209209
210210
{
211-
// Required: Source code language. Currently supported are "Solidity", "Yul" and "SolidityAST" (experimental).
211+
// Required: Source code language. Currently supported are "Solidity", "Yul", "SolidityAST" (experimental), "EVMAssembly" (experimental).
212212
"language": "Solidity",
213213
// Required
214214
"sources":
@@ -235,21 +235,40 @@ Input Description
235235
// If files are used, their directories should be added to the command-line via
236236
// `--allow-paths <path>`.
237237
]
238-
// If language is set to "SolidityAST", an AST needs to be supplied under the "ast" key.
238+
},
239+
"destructible":
240+
{
241+
// Optional: keccak256 hash of the source file
242+
"keccak256": "0x234...",
243+
// Required (unless "urls" is used): literal contents of the source file
244+
"content": "contract destructible is owned { function shutdown() { if (msg.sender == owner) selfdestruct(owner); } }"
245+
},
246+
"myFile.sol_json.ast":
247+
{
248+
// If language is set to "SolidityAST", an AST needs to be supplied under the "ast" key
249+
// and there can be only one source file present.
250+
// The format is the same as used by the `ast` output.
239251
// Note that importing ASTs is experimental and in particular that:
240252
// - importing invalid ASTs can produce undefined results and
241253
// - no proper error reporting is available on invalid ASTs.
242254
// Furthermore, note that the AST import only consumes the fields of the AST as
243255
// produced by the compiler in "stopAfter": "parsing" mode and then re-performs
244256
// analysis, so any analysis-based annotations of the AST are ignored upon import.
245-
"ast": { ... } // formatted as the json ast requested with the ``ast`` output selection.
257+
"ast": { ... }
246258
},
247-
"destructible":
259+
"myFile_evm.json":
248260
{
249-
// Optional: keccak256 hash of the source file
250-
"keccak256": "0x234...",
251-
// Required (unless "urls" is used): literal contents of the source file
252-
"content": "contract destructible is owned { function shutdown() { if (msg.sender == owner) selfdestruct(owner); } }"
261+
// If language is set to "EVMAssembly", an EVM Assembly JSON object needs to be supplied
262+
// under the "assemblyJson" key and there can be only one source file present.
263+
// The format is the same as used by the `evm.legacyAssembly` output or `--asm-json`
264+
// output on the command line.
265+
// Note that importing EVM assembly is experimental.
266+
"assemblyJson":
267+
{
268+
".code": [ ... ],
269+
".data": { ... }, // optional
270+
"sourceList": [ ... ] // optional (if no `source` node was defined in any `.code` object)
271+
}
253272
}
254273
},
255274
// Optional

Diff for: libevmasm/EVMAssemblyStack.cpp

+8-3
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,16 @@ namespace solidity::evmasm
3636

3737
void EVMAssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source)
3838
{
39-
solAssert(!m_evmAssembly);
40-
m_name = _sourceName;
4139
Json::Value assemblyJson;
4240
solRequire(jsonParseStrict(_source, assemblyJson), AssemblyImportException, "Could not parse JSON file.");
43-
std::tie(m_evmAssembly, m_sourceList) = evmasm::Assembly::fromJSON(assemblyJson);
41+
analyze(_sourceName, assemblyJson);
42+
}
43+
44+
void EVMAssemblyStack::analyze(std::string const& _sourceName, Json::Value const& _assemblyJson)
45+
{
46+
solAssert(!m_evmAssembly);
47+
m_name = _sourceName;
48+
std::tie(m_evmAssembly, m_sourceList) = evmasm::Assembly::fromJSON(_assemblyJson);
4449
solRequire(m_evmAssembly != nullptr, AssemblyImportException, "Could not create evm assembly object.");
4550
}
4651

Diff for: libevmasm/EVMAssemblyStack.h

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ class EVMAssemblyStack: public AbstractAssemblyStack
4040
/// @throws AssemblyImportException, if JSON could not be validated.
4141
void parseAndAnalyze(std::string const& _sourceName, std::string const& _source);
4242

43+
/// Runs analysis steps.
44+
/// Multiple calls overwrite the previous state.
45+
/// @throws AssemblyImportException, if JSON could not be validated.
46+
void analyze(std::string const& _sourceName, Json::Value const& _assemblyJson);
47+
4348
void assemble();
4449

4550
std::string const& name() const { return m_name; }

Diff for: libsolidity/interface/StandardCompiler.cpp

+133-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <libyul/optimiser/Suite.h>
3131

3232
#include <libevmasm/Disassemble.h>
33+
#include <libevmasm/EVMAssemblyStack.h>
3334

3435
#include <libsmtutil/Exceptions.h>
3536

@@ -730,7 +731,29 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
730731
for (auto const& sourceName: sources.getMemberNames())
731732
ret.sources[sourceName] = util::jsonCompactPrint(sources[sourceName]);
732733
}
734+
else if (ret.language == "EVMAssembly")
735+
{
736+
for (std::string const& sourceName: sources.getMemberNames())
737+
{
738+
solAssert(sources.isMember(sourceName));
739+
if (
740+
!sources[sourceName].isMember("assemblyJson") ||
741+
!sources[sourceName]["assemblyJson"].isObject() ||
742+
sources[sourceName].size() != 1
743+
)
744+
return formatFatalError(
745+
Error::Type::JSONError,
746+
"Invalid input source specified. Expected exactly one object, named 'assemblyJson', inside $.sources." + sourceName
747+
);
733748

749+
ret.jsonSources[sourceName] = sources[sourceName]["assemblyJson"];
750+
}
751+
if (ret.jsonSources.size() != 1)
752+
return formatFatalError(
753+
Error::Type::JSONError,
754+
"EVMAssembly import only supports exactly one input file."
755+
);
756+
}
734757
Json::Value const& auxInputs = _input["auxiliaryInput"];
735758

736759
if (auto result = checkAuxiliaryInputKeys(auxInputs))
@@ -1164,8 +1187,113 @@ std::map<std::string, Json::Value> StandardCompiler::parseAstFromInput(StringMap
11641187
return sourceJsons;
11651188
}
11661189

1190+
Json::Value StandardCompiler::importEVMAssembly(StandardCompiler::InputsAndSettings _inputsAndSettings)
1191+
{
1192+
solAssert(_inputsAndSettings.language == "EVMAssembly");
1193+
solAssert(_inputsAndSettings.sources.empty());
1194+
solAssert(_inputsAndSettings.jsonSources.size() == 1);
1195+
1196+
if (!isBinaryRequested(_inputsAndSettings.outputSelection))
1197+
return Json::objectValue;
1198+
1199+
evmasm::EVMAssemblyStack stack(_inputsAndSettings.evmVersion);
1200+
std::string const& sourceName = _inputsAndSettings.jsonSources.begin()->first; // result of structured binding can only be used within lambda from C++20 on.
1201+
Json::Value const& sourceJson = _inputsAndSettings.jsonSources.begin()->second;
1202+
try
1203+
{
1204+
stack.analyze(sourceName, sourceJson);
1205+
stack.assemble();
1206+
}
1207+
catch (evmasm::AssemblyImportException const& e)
1208+
{
1209+
return formatFatalError(Error::Type::Exception, "Assembly import error: " + std::string(e.what()));
1210+
}
1211+
catch (evmasm::InvalidOpcode const& e)
1212+
{
1213+
return formatFatalError(Error::Type::Exception, "Assembly import error: " + std::string(e.what()));
1214+
}
1215+
catch (...)
1216+
{
1217+
return formatError(
1218+
Error::Type::Exception,
1219+
"general",
1220+
"Unknown exception during assembly import: " + boost::current_exception_diagnostic_information()
1221+
);
1222+
}
1223+
if (!stack.compilationSuccessful())
1224+
return Json::objectValue;
1225+
1226+
// EVM
1227+
bool const wildcardMatchesExperimental = false;
1228+
Json::Value evmData = Json::objectValue;
1229+
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "evm.assembly", wildcardMatchesExperimental))
1230+
evmData["assembly"] = stack.assemblyString(sourceName, {});
1231+
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "evm.legacyAssembly", wildcardMatchesExperimental))
1232+
evmData["legacyAssembly"] = stack.assemblyJSON(sourceName);
1233+
1234+
if (isArtifactRequested(
1235+
_inputsAndSettings.outputSelection,
1236+
sourceName,
1237+
"",
1238+
evmObjectComponents("bytecode"),
1239+
wildcardMatchesExperimental
1240+
))
1241+
evmData["bytecode"] = collectEVMObject(
1242+
_inputsAndSettings.evmVersion,
1243+
stack.object(sourceName),
1244+
stack.sourceMapping(sourceName),
1245+
{},
1246+
false, // _runtimeObject
1247+
[&](std::string const& _element) {
1248+
return isArtifactRequested(
1249+
_inputsAndSettings.outputSelection,
1250+
sourceName,
1251+
"",
1252+
"evm.bytecode." + _element,
1253+
wildcardMatchesExperimental
1254+
);
1255+
}
1256+
);
1257+
1258+
if (isArtifactRequested(
1259+
_inputsAndSettings.outputSelection,
1260+
sourceName,
1261+
"",
1262+
evmObjectComponents("deployedBytecode"),
1263+
wildcardMatchesExperimental
1264+
))
1265+
evmData["deployedBytecode"] = collectEVMObject(
1266+
_inputsAndSettings.evmVersion,
1267+
stack.runtimeObject(sourceName),
1268+
stack.runtimeSourceMapping(sourceName),
1269+
{},
1270+
true, // _runtimeObject
1271+
[&](std::string const& _element) {
1272+
return isArtifactRequested(
1273+
_inputsAndSettings.outputSelection,
1274+
sourceName,
1275+
"",
1276+
"evm.deployedBytecode." + _element,
1277+
wildcardMatchesExperimental
1278+
);
1279+
}
1280+
);
1281+
1282+
Json::Value contractData = Json::objectValue;
1283+
if (!evmData.empty())
1284+
contractData["evm"] = evmData;
1285+
1286+
Json::Value contractsOutput = Json::objectValue;
1287+
contractsOutput[sourceName][""] = contractData;
1288+
Json::Value output = Json::objectValue;
1289+
output["contracts"] = contractsOutput;
1290+
return util::removeNullMembers(output);
1291+
}
1292+
11671293
Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings)
11681294
{
1295+
solAssert(_inputsAndSettings.jsonSources.empty());
1296+
11691297
CompilerStack compilerStack(m_readFile);
11701298

11711299
StringMap sourceList = std::move(_inputsAndSettings.sources);
@@ -1470,6 +1598,8 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
14701598

14711599
Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
14721600
{
1601+
solAssert(_inputsAndSettings.jsonSources.empty());
1602+
14731603
Json::Value output = Json::objectValue;
14741604
output["errors"] = std::move(_inputsAndSettings.errors);
14751605

@@ -1627,8 +1757,10 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
16271757
return compileYul(std::move(settings));
16281758
else if (settings.language == "SolidityAST")
16291759
return compileSolidity(std::move(settings));
1760+
else if (settings.language == "EVMAssembly")
1761+
return importEVMAssembly(std::move(settings));
16301762
else
1631-
return formatFatalError(Error::Type::JSONError, "Only \"Solidity\", \"Yul\" or \"SolidityAST\" is supported as a language.");
1763+
return formatFatalError(Error::Type::JSONError, "Only \"Solidity\", \"Yul\", \"SolidityAST\" or \"EVMAssembly\" is supported as a language.");
16321764
}
16331765
catch (Json::LogicError const& _exception)
16341766
{

Diff for: libsolidity/interface/StandardCompiler.h

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class StandardCompiler
7474
Json::Value errors;
7575
CompilerStack::State stopAfter = CompilerStack::State::CompilationSuccessful;
7676
std::map<std::string, std::string> sources;
77+
std::map<std::string, Json::Value> jsonSources;
7778
std::map<util::h256, std::string> smtLib2Responses;
7879
langutil::EVMVersion evmVersion;
7980
std::optional<uint8_t> eofVersion;
@@ -95,6 +96,7 @@ class StandardCompiler
9596
std::variant<InputsAndSettings, Json::Value> parseInput(Json::Value const& _input);
9697

9798
std::map<std::string, Json::Value> parseAstFromInput(StringMap const& _sources);
99+
Json::Value importEVMAssembly(InputsAndSettings _inputsAndSettings);
98100
Json::Value compileSolidity(InputsAndSettings _inputsAndSettings);
99101
Json::Value compileYul(InputsAndSettings _inputsAndSettings);
100102

Diff for: test/cmdlineTests/standard_import_evmasm/input.json

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"language": "EVMAssembly",
3+
"sources": {
4+
"A": {
5+
"assemblyJson": {
6+
".code": [
7+
{
8+
"begin": 36,
9+
"end": 51,
10+
"name": "PUSH",
11+
"source": 0,
12+
"value": "0"
13+
}
14+
],
15+
"sourceList": [
16+
"<stdin>"
17+
]
18+
}
19+
}
20+
},
21+
"settings": {
22+
"outputSelection": {
23+
"*": {
24+
"": ["*"]
25+
}
26+
}
27+
}
28+
}

Diff for: test/cmdlineTests/standard_import_evmasm/output.json

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"contracts":
3+
{
4+
"A":
5+
{
6+
"":
7+
{
8+
"evm":
9+
{
10+
"assembly": " /* \"<stdin>\":36:51 */
11+
0x00
12+
",
13+
"bytecode":
14+
{
15+
"functionDebugData": {},
16+
"linkReferences": {},
17+
"object": "<BYTECODE REMOVED>",
18+
"opcodes":"<OPCODES REMOVED>",
19+
"sourceMap":"<SOURCEMAP REMOVED>"
20+
},
21+
"deployedBytecode":
22+
{
23+
"functionDebugData": {},
24+
"immutableReferences": {},
25+
"linkReferences": {},
26+
"object": "",
27+
"opcodes": "",
28+
"sourceMap": ""
29+
},
30+
"legacyAssembly":
31+
{
32+
".code":
33+
[
34+
{
35+
"begin": 36,
36+
"end": 51,
37+
"name": "PUSH",
38+
"source": 0,
39+
"value": "0"
40+
}
41+
],
42+
"sourceList":
43+
[
44+
"<stdin>"
45+
]
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)