Skip to content

Commit f928c71

Browse files
committed
EVM assembly test case with JSON input
1 parent 4c4d45d commit f928c71

21 files changed

+446
-4
lines changed

Diff for: test/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ detect_stray_source_files("${libsolutil_sources}" "libsolutil/")
5151

5252
set(libevmasm_sources
5353
libevmasm/Assembler.cpp
54+
libevmasm/EVMAssemblyTest.cpp
55+
libevmasm/EVMAssemblyTest.h
5456
libevmasm/Optimiser.cpp
5557
)
5658
detect_stray_source_files("${libevmasm_sources}" "libevmasm/")

Diff for: test/InteractiveTests.h

+3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
#include <test/libyul/StackShufflingTest.h>
4343
#include <test/libyul/SyntaxTest.h>
4444

45+
#include <test/libevmasm/EVMAssemblyTest.h>
46+
4547
#include <boost/filesystem.hpp>
4648

4749
namespace solidity::frontend::test
@@ -64,6 +66,7 @@ struct Testsuite
6466
Testsuite const g_interactiveTestsuites[] = {
6567
/*
6668
Title Path Subpath SMT NeedsVM Creator function */
69+
{"EVM Assembly", "libevmasm", "evmAssemblyTests", false, false, &evmasm::test::EVMAssemblyTest::create},
6770
{"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create},
6871
{"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create},
6972
{"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create},

Diff for: test/TestCase.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ void TestCase::printUpdatedSettings(std::ostream& _stream, std::string const& _l
5151
bool TestCase::isTestFilename(boost::filesystem::path const& _filename)
5252
{
5353
std::string extension = _filename.extension().string();
54-
return (extension == ".sol" || extension == ".yul" || extension == ".stack") &&
55-
!boost::starts_with(_filename.string(), "~") &&
56-
!boost::starts_with(_filename.string(), ".");
54+
// NOTE: .asmjson rather than .json because JSON files that do not represent test cases exist in some test dirs.
55+
return (extension == ".sol" || extension == ".yul" || extension == ".asmjson" || extension == ".stack") &&
56+
!boost::starts_with(_filename.string(), "~") &&
57+
!boost::starts_with(_filename.string(), ".");
5758
}
5859

5960
bool TestCase::shouldRun()

Diff for: test/libevmasm/EVMAssemblyTest.cpp

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
#include <test/libevmasm/EVMAssemblyTest.h>
19+
20+
#include <test/Common.h>
21+
22+
#include <libevmasm/Disassemble.h>
23+
#include <libevmasm/EVMAssemblyStack.h>
24+
25+
#include <boost/algorithm/string/predicate.hpp>
26+
#include <boost/algorithm/string/split.hpp>
27+
#include <boost/algorithm/string/trim.hpp>
28+
29+
#include <range/v3/view/map.hpp>
30+
31+
using namespace std::string_literals;
32+
using namespace solidity;
33+
using namespace solidity::test;
34+
using namespace solidity::evmasm;
35+
using namespace solidity::evmasm::test;
36+
using namespace solidity::frontend;
37+
using namespace solidity::frontend::test;
38+
using namespace solidity::langutil;
39+
using namespace solidity::util;
40+
41+
std::vector<std::string> const EVMAssemblyTest::c_outputLabels = {
42+
"Assembly",
43+
"Bytecode",
44+
"Opcodes",
45+
"SourceMappings",
46+
};
47+
48+
std::unique_ptr<TestCase> EVMAssemblyTest::create(Config const& _config)
49+
{
50+
return std::make_unique<EVMAssemblyTest>(_config.filename);
51+
}
52+
53+
EVMAssemblyTest::EVMAssemblyTest(std::string const& _filename):
54+
EVMVersionRestrictedTestCase(_filename)
55+
{
56+
m_source = m_reader.source();
57+
m_expectation = m_reader.simpleExpectations();
58+
59+
if (!boost::algorithm::ends_with(_filename, ".asmjson"))
60+
BOOST_THROW_EXCEPTION(std::runtime_error("Not an assembly test: \"" + _filename + "\". Allowed extensions: .asmjson."));
61+
62+
m_selectedOutputs = m_reader.stringSetting("outputs", "Assembly,Bytecode,Opcodes,SourceMappings");
63+
OptimisationPreset optimizationPreset = m_reader.enumSetting<OptimisationPreset>(
64+
"optimizationPreset",
65+
{
66+
{"none", OptimisationPreset::None},
67+
{"minimal", OptimisationPreset::Minimal},
68+
{"standard", OptimisationPreset::Standard},
69+
{"full", OptimisationPreset::Full},
70+
},
71+
"none"
72+
);
73+
m_optimizerSettings = Assembly::OptimiserSettings::translateSettings(OptimiserSettings::preset(optimizationPreset));
74+
m_optimizerSettings.expectedExecutionsPerDeployment = m_reader.sizetSetting(
75+
"optimizer.expectedExecutionsPerDeployment",
76+
m_optimizerSettings.expectedExecutionsPerDeployment
77+
);
78+
79+
auto const optimizerComponentSetting = [&](std::string const& _component, bool& _setting) {
80+
_setting = m_reader.boolSetting("optimizer." + _component, _setting);
81+
};
82+
optimizerComponentSetting("inliner", m_optimizerSettings.runInliner);
83+
optimizerComponentSetting("jumpdestRemover", m_optimizerSettings.runJumpdestRemover);
84+
optimizerComponentSetting("peephole", m_optimizerSettings.runPeephole);
85+
optimizerComponentSetting("deduplicate", m_optimizerSettings.runDeduplicate);
86+
optimizerComponentSetting("cse", m_optimizerSettings.runCSE);
87+
optimizerComponentSetting("constantOptimizer", m_optimizerSettings.runConstantOptimiser);
88+
89+
// TODO: Enable when assembly import for EOF is implemented.
90+
if (CommonOptions::get().eofVersion().has_value())
91+
m_shouldRun = false;
92+
}
93+
94+
TestCase::TestResult EVMAssemblyTest::run(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted)
95+
{
96+
EVMAssemblyStack evmAssemblyStack(
97+
CommonOptions::get().evmVersion(),
98+
CommonOptions::get().eofVersion(),
99+
m_optimizerSettings
100+
);
101+
102+
evmAssemblyStack.selectDebugInfo(DebugInfoSelection::AllExceptExperimental());
103+
104+
try
105+
{
106+
evmAssemblyStack.parseAndAnalyze(m_reader.fileName().filename().string(), m_source);
107+
}
108+
catch (AssemblyImportException const& _exception)
109+
{
110+
m_obtainedResult = "AssemblyImportException: "s + _exception.what() + "\n";
111+
return checkResult(_stream, _linePrefix, _formatted);
112+
}
113+
114+
try
115+
{
116+
evmAssemblyStack.assemble();
117+
}
118+
catch (Error const& _error)
119+
{
120+
// TODO: EVMAssemblyStack should catch these on its own and provide an error reporter.
121+
soltestAssert(_error.comment(), "Errors must include a message for the user.");
122+
m_obtainedResult = Error::formatErrorType(_error.type()) + ": " + *_error.comment() + "\n";
123+
return checkResult(_stream, _linePrefix, _formatted);
124+
}
125+
soltestAssert(evmAssemblyStack.compilationSuccessful());
126+
127+
auto const produceOutput = [&](std::string const& _output) {
128+
if (_output == "Assembly")
129+
return evmAssemblyStack.assemblyString({{m_reader.fileName().filename().string(), m_source}});
130+
if (_output == "Bytecode")
131+
return util::toHex(evmAssemblyStack.object().bytecode);
132+
if (_output == "Opcodes")
133+
return disassemble(evmAssemblyStack.object().bytecode, CommonOptions::get().evmVersion());
134+
if (_output == "SourceMappings")
135+
return evmAssemblyStack.sourceMapping();
136+
soltestAssert(false);
137+
unreachable();
138+
};
139+
140+
std::set<std::string> selectedOutputSet;
141+
boost::split(selectedOutputSet, m_selectedOutputs, boost::is_any_of(","));
142+
for (std::string const& output: c_outputLabels)
143+
if (selectedOutputSet.contains(output))
144+
{
145+
if (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n')
146+
m_obtainedResult += "\n";
147+
148+
// Don't trim on the left to avoid stripping indentation.
149+
std::string content = produceOutput(output);
150+
boost::trim_right(content);
151+
std::string separator = (content.empty() ? "" : (output == "Assembly" ? "\n" : " "));
152+
m_obtainedResult += output + ":" + separator + content;
153+
}
154+
if (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n')
155+
m_obtainedResult += "\n";
156+
157+
return checkResult(_stream, _linePrefix, _formatted);
158+
}

Diff for: test/libevmasm/EVMAssemblyTest.h

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#pragma once
20+
21+
#include <test/TestCase.h>
22+
23+
#include <libsolidity/interface/OptimiserSettings.h>
24+
25+
#include <libevmasm/Assembly.h>
26+
27+
#include <memory>
28+
#include <ostream>
29+
#include <string>
30+
#include <vector>
31+
32+
namespace solidity::evmasm::test
33+
{
34+
35+
class EVMAssemblyTest: public frontend::test::EVMVersionRestrictedTestCase
36+
{
37+
public:
38+
static std::unique_ptr<TestCase> create(Config const& _config);
39+
40+
EVMAssemblyTest(std::string const& _filename);
41+
42+
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override;
43+
44+
private:
45+
static std::vector<std::string> const c_outputLabels;
46+
47+
std::string m_selectedOutputs;
48+
evmasm::Assembly::OptimiserSettings m_optimizerSettings;
49+
};
50+
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
".code": [
3+
{"name": "PUSH [$]", "value": "0"}
4+
],
5+
".data": {
6+
"0": {
7+
".code": [
8+
{"name": "PUSHIMMUTABLE", "value": "x"}
9+
]
10+
}
11+
}
12+
}
13+
// ----
14+
// CodeGenerationError: Some immutables were read from but never assigned, possibly because of optimization.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
// ----
3+
// AssemblyImportException: Could not parse JSON file.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
".code": [
3+
{"name": "CODESIZE"}
4+
]
5+
}
6+
// ====
7+
// bytecodeFormat: legacy
8+
// ----
9+
// Assembly:
10+
// codesize
11+
// Bytecode: 38
12+
// Opcodes: CODESIZE
13+
// SourceMappings: :::-:0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
".code": [
3+
{"name": "BLOBBASEFEE"}
4+
]
5+
}
6+
// ====
7+
// EVMVersion: >=cancun
8+
// ----
9+
// Assembly:
10+
// blobbasefee
11+
// Bytecode: 4a
12+
// Opcodes: BLOBBASEFEE
13+
// SourceMappings: :::-:0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
".code": [
3+
{"name": "PUSH", "value": "ffffffffffffffff"}
4+
]
5+
}
6+
// ====
7+
// optimizationPreset: none
8+
// optimizer.constantOptimizer: true
9+
// ----
10+
// Assembly:
11+
// sub(shl(0x40, 0x01), 0x01)
12+
// Bytecode: 6001600160401b03
13+
// Opcodes: PUSH1 0x1 PUSH1 0x1 PUSH1 0x40 SHL SUB
14+
// SourceMappings: :::-:0;;;;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
".code": [
3+
{"name": "PUSH", "value": "0"},
4+
{"name": "DUP2"},
5+
{"name": "SUB"}
6+
]
7+
}
8+
// ====
9+
// optimizationPreset: none
10+
// optimizer.cse: true
11+
// ----
12+
// Assembly:
13+
// dup1
14+
// Bytecode: 80
15+
// Opcodes: DUP1
16+
// SourceMappings: :::-:0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
".code": [
3+
{"name": "PUSH [tag]", "value": "1"},
4+
{"name": "PUSH [tag]", "value": "2"},
5+
{"name": "tag", "value": "1"},
6+
{"name": "JUMPDEST"},
7+
{"name": "JUMP"},
8+
{"name": "tag", "value": "2"},
9+
{"name": "JUMPDEST"},
10+
{"name": "JUMP"}
11+
]
12+
}
13+
// ====
14+
// optimizationPreset: none
15+
// optimizer.deduplicate: true
16+
// ----
17+
// Assembly:
18+
// tag_1
19+
// tag_1
20+
// tag_1:
21+
// jump
22+
// tag_2:
23+
// jump
24+
// Bytecode: 600460045b565b56
25+
// Opcodes: PUSH1 0x4 PUSH1 0x4 JUMPDEST JUMP JUMPDEST JUMP
26+
// SourceMappings: :::-:0;;;;;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
".code": [
3+
{"name": "PUSH [$]", "value": "0"}
4+
],
5+
".data": {
6+
"0": {
7+
".code": [
8+
{"name": "PUSH", "value": "ffffffffffffffff"}
9+
]
10+
}
11+
}
12+
}
13+
// ====
14+
// optimizationPreset: standard
15+
// optimizer.expectedExecutionsPerDeployment: 0
16+
// ----
17+
// Assembly:
18+
// dataOffset(sub_0)
19+
// stop
20+
//
21+
// sub_0: assembly {
22+
// sub(shl(0x40, 0x01), 0x01)
23+
// }
24+
// Bytecode: 6003fe6001600160401b03
25+
// Opcodes: PUSH1 0x3 INVALID PUSH1 0x1 PUSH1 0x1 PUSH1 0x40 SHL SUB
26+
// SourceMappings: :::-:0

0 commit comments

Comments
 (0)