diff --git a/libevmasm/CommonSubexpressionEliminator.h b/libevmasm/CommonSubexpressionEliminator.h index a09b1913f..cddc928aa 100644 --- a/libevmasm/CommonSubexpressionEliminator.h +++ b/libevmasm/CommonSubexpressionEliminator.h @@ -161,7 +161,7 @@ private: /// Current positions of equivalence classes, equal to the empty set if already deleted. std::map> m_classPositions; - /// The actual eqivalence class items and how to compute them. + /// The actual equivalence class items and how to compute them. ExpressionClasses& m_expressionClasses; /// Keeps information about which storage or memory slots were written to by which operations. /// The operations are sorted ascendingly by sequence number. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 59603442d..acc43b68d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,7 +30,7 @@ add_executable(soltest ${sources} ${headers} ${libsolidity_sources} ${libsolidity_headers} ${libsolidity_util_sources} ${libsolidity_util_headers} ) -target_link_libraries(soltest PRIVATE libsolc yul solidity evmasm devcore ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) +target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) if (LLL) target_link_libraries(soltest PRIVATE lll) diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index a085fc323..f945ec296 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,7 @@ Testsuite const g_interactiveTestsuites[] = { /* Title Path Subpath SMT IPC Creator function */ {"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create}, + {"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create}, {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp new file mode 100644 index 000000000..4a699ca90 --- /dev/null +++ b/test/libyul/YulInterpreterTest.cpp @@ -0,0 +1,160 @@ +/* + 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 . +*/ + +#include + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +using namespace dev; +using namespace langutil; +using namespace yul; +using namespace yul::test; +using namespace dev::solidity; +using namespace dev::solidity::test; +using namespace std; + +YulInterpreterTest::YulInterpreterTest(string const& _filename) +{ + boost::filesystem::path path(_filename); + + ifstream file(_filename); + if (!file) + BOOST_THROW_EXCEPTION(runtime_error("Cannot open test case: \"" + _filename + "\".")); + file.exceptions(ios::badbit); + + string line; + while (getline(file, line)) + { + if (boost::algorithm::starts_with(line, "// ----")) + break; + m_source += std::move(line) + "\n"; + } + while (getline(file, line)) + if (boost::algorithm::starts_with(line, "// ")) + m_expectation += line.substr(3) + "\n"; + else + m_expectation += line + "\n"; +} + +bool YulInterpreterTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + if (!parse(_stream, _linePrefix, _formatted)) + return false; + + m_obtainedResult = interpret(); + + if (m_expectation != m_obtainedResult) + { + string nextIndentLevel = _linePrefix + " "; + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Expected result:" << endl; + // TODO could compute a simple diff with highlighted lines + printIndented(_stream, m_expectation, nextIndentLevel); + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Obtained result:" << endl; + printIndented(_stream, m_obtainedResult, nextIndentLevel); + return false; + } + return true; +} + +void YulInterpreterTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const +{ + printIndented(_stream, m_source, _linePrefix); +} + +void YulInterpreterTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const +{ + printIndented(_stream, m_obtainedResult, _linePrefix); +} + +void YulInterpreterTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const +{ + stringstream output(_output); + string line; + while (getline(output, line)) + _stream << _linePrefix << line << endl; +} + +bool YulInterpreterTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + AssemblyStack stack(dev::test::Options::get().evmVersion(), AssemblyStack::Language::StrictAssembly); + if (stack.parseAndAnalyze("", m_source)) + { + m_ast = stack.parserResult()->code; + m_analysisInfo = stack.parserResult()->analysisInfo; + return true; + } + else + { + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl; + printErrors(_stream, stack.errors()); + return false; + } +} + +string YulInterpreterTest::interpret() +{ + InterpreterState state; + state.maxTraceSize = 10000; + Interpreter interpreter(state); + try + { + interpreter(*m_ast); + } + catch (InterpreterTerminated const&) + { + } + + stringstream result; + result << "Trace:" << endl;; + for (auto const& line: interpreter.trace()) + result << " " << line << endl; + result << "Memory dump:\n"; + for (size_t i = 0; i < state.memory.size(); i += 0x20) + result << " " << std::hex << std::setw(4) << i << ": " << toHex(bytesConstRef(state.memory.data() + i, 0x20).toBytes()) << endl; + result << "Storage dump:" << endl; + for (auto const& slot: state.storage) + result << " " << slot.first.hex() << ": " << slot.second.hex() << endl; + return result.str(); +} + +void YulInterpreterTest::printErrors(ostream& _stream, ErrorList const& _errors) +{ + SourceReferenceFormatter formatter(_stream); + + for (auto const& error: _errors) + formatter.printExceptionInformation( + *error, + (error->type() == Error::Type::Warning) ? "Warning" : "Error" + ); +} diff --git a/test/libyul/YulInterpreterTest.h b/test/libyul/YulInterpreterTest.h new file mode 100644 index 000000000..24daa2e2b --- /dev/null +++ b/test/libyul/YulInterpreterTest.h @@ -0,0 +1,72 @@ +/* + 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 . +*/ + +#pragma once + +#include + +namespace langutil +{ +class Scanner; +class Error; +using ErrorList = std::vector>; +} + +namespace yul +{ +struct AsmAnalysisInfo; +struct Block; +struct Dialect; +} + +namespace yul +{ +namespace test +{ + +class YulInterpreterTest: public dev::solidity::test::TestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { + return std::unique_ptr(new YulInterpreterTest(_config.filename)); + } + + explicit YulInterpreterTest(std::string const& _filename); + + bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + + void printSource(std::ostream& _stream, std::string const &_linePrefix = "", bool const _formatted = false) const override; + void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; + +private: + void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; + bool parse(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted); + std::string interpret(); + + static void printErrors(std::ostream& _stream, langutil::ErrorList const& _errors); + + std::string m_source; + std::string m_expectation; + + std::shared_ptr m_ast; + std::shared_ptr m_analysisInfo; + std::string m_obtainedResult; +}; + +} +} diff --git a/test/libyul/yulInterpreterTests/ambiguous_vars.yul b/test/libyul/yulInterpreterTests/ambiguous_vars.yul new file mode 100644 index 000000000..c5c723e99 --- /dev/null +++ b/test/libyul/yulInterpreterTests/ambiguous_vars.yul @@ -0,0 +1,16 @@ +{ + { + let a := 0x20 + mstore(a, 2) + } + let a + mstore(a, 3) +} +// ---- +// Trace: +// MSTORE_AT_SIZE(32, 32) [0000000000000000000000000000000000000000000000000000000000000002] +// MSTORE_AT_SIZE(0, 32) [0000000000000000000000000000000000000000000000000000000000000003] +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000003 +// 20: 0000000000000000000000000000000000000000000000000000000000000002 +// Storage dump: diff --git a/test/libyul/yulInterpreterTests/external_call.yul b/test/libyul/yulInterpreterTests/external_call.yul new file mode 100644 index 000000000..a7e0c6bae --- /dev/null +++ b/test/libyul/yulInterpreterTests/external_call.yul @@ -0,0 +1,17 @@ +{ + let x := call(gas(), 0x45, 0x5, 0, 0x20, 0x30, 0x20) + sstore(100, x) +} +// ---- +// Trace: +// GAS() +// MLOAD_FROM_SIZE(0, 32) +// MSTORE_AT_SIZE(48, 32) +// CALL(153, 69, 5, 0, 32, 48, 32) +// SSTORE(100, 1) +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000000 +// 20: 0000000000000000000000000000000000000000000000000000000000000000 +// 40: 0000000000000000000000000000000000000000000000000000000000000000 +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000064: 0000000000000000000000000000000000000000000000000000000000000001 diff --git a/test/libyul/yulInterpreterTests/function_calls.yul b/test/libyul/yulInterpreterTests/function_calls.yul new file mode 100644 index 000000000..3738a9a2c --- /dev/null +++ b/test/libyul/yulInterpreterTests/function_calls.yul @@ -0,0 +1,14 @@ +{ + function f(a, b) -> x, y { + x := add(a, b) + y := mul(a, b) + } + let r, t := f(6, 7) + sstore(r, t) +} +// ---- +// Trace: +// SSTORE(13, 42) +// Memory dump: +// Storage dump: +// 000000000000000000000000000000000000000000000000000000000000000d: 000000000000000000000000000000000000000000000000000000000000002a diff --git a/test/libyul/yulInterpreterTests/loop.yul b/test/libyul/yulInterpreterTests/loop.yul new file mode 100644 index 000000000..45f584ab0 --- /dev/null +++ b/test/libyul/yulInterpreterTests/loop.yul @@ -0,0 +1,20 @@ +{ + for { let x := 2 } lt(x, 10) { x := add(x, 1) } { + mstore(mul(x, 5), mul(x, 0x1000)) + } +} +// ---- +// Trace: +// MSTORE_AT_SIZE(10, 32) [0000000000000000000000000000000000000000000000000000000000002000] +// MSTORE_AT_SIZE(15, 32) [0000000000000000000000000000000000000000000000000000000000003000] +// MSTORE_AT_SIZE(20, 32) [0000000000000000000000000000000000000000000000000000000000004000] +// MSTORE_AT_SIZE(25, 32) [0000000000000000000000000000000000000000000000000000000000005000] +// MSTORE_AT_SIZE(30, 32) [0000000000000000000000000000000000000000000000000000000000006000] +// MSTORE_AT_SIZE(35, 32) [0000000000000000000000000000000000000000000000000000000000007000] +// MSTORE_AT_SIZE(40, 32) [0000000000000000000000000000000000000000000000000000000000008000] +// MSTORE_AT_SIZE(45, 32) [0000000000000000000000000000000000000000000000000000000000009000] +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000000 +// 20: 0000000000000000000000000000000000000000000000000000000000000000 +// 40: 0000000000000000000000900000000000000000000000000000000000000000 +// Storage dump: diff --git a/test/libyul/yulInterpreterTests/simple_mstore.yul b/test/libyul/yulInterpreterTests/simple_mstore.yul new file mode 100644 index 000000000..879b03801 --- /dev/null +++ b/test/libyul/yulInterpreterTests/simple_mstore.yul @@ -0,0 +1,10 @@ +{ + mstore(10, 11) +} +// ---- +// Trace: +// MSTORE_AT_SIZE(10, 32) [000000000000000000000000000000000000000000000000000000000000000b] +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000000 +// 20: 0000000000000000000b00000000000000000000000000000000000000000000 +// Storage dump: diff --git a/test/libyul/yulInterpreterTests/smoke.yul b/test/libyul/yulInterpreterTests/smoke.yul new file mode 100644 index 000000000..f7f1a1aef --- /dev/null +++ b/test/libyul/yulInterpreterTests/smoke.yul @@ -0,0 +1,5 @@ +{} +// ---- +// Trace: +// Memory dump: +// Storage dump: diff --git a/test/libyul/yulInterpreterTests/switch_statement.yul b/test/libyul/yulInterpreterTests/switch_statement.yul new file mode 100644 index 000000000..06ac3703a --- /dev/null +++ b/test/libyul/yulInterpreterTests/switch_statement.yul @@ -0,0 +1,13 @@ +{ + switch 7 + case 7 { mstore(1, 2) } + case 3 { mstore(6, 7) } + default { mstore(8, 9) } +} +// ---- +// Trace: +// MSTORE_AT_SIZE(1, 32) [0000000000000000000000000000000000000000000000000000000000000002] +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000000 +// 20: 0200000000000000000000000000000000000000000000000000000000000000 +// Storage dump: diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 3f901ef76..2cc31170f 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -2,6 +2,10 @@ if (OSSFUZZ) add_subdirectory(ossfuzz) endif() +add_subdirectory(yulInterpreter) +add_executable(yulrun yulrun.cpp) +target_link_libraries(yulrun PRIVATE yulInterpreter libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES}) + add_executable(solfuzzer afl_fuzzer.cpp fuzzer_common.cpp) target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES}) @@ -26,5 +30,6 @@ add_executable(isoltest ../libsolidity/SMTCheckerJSONTest.cpp ../libyul/ObjectCompilerTest.cpp ../libyul/YulOptimizerTest.cpp + ../libyul/YulInterpreterTest.cpp ) -target_link_libraries(isoltest PRIVATE libsolc solidity evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) +target_link_libraries(isoltest PRIVATE libsolc solidity yulInterpreter evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) diff --git a/test/tools/yulInterpreter/CMakeLists.txt b/test/tools/yulInterpreter/CMakeLists.txt new file mode 100644 index 000000000..52fe0e3c6 --- /dev/null +++ b/test/tools/yulInterpreter/CMakeLists.txt @@ -0,0 +1,9 @@ +set(sources + EVMInstructionInterpreter.h + EVMInstructionInterpreter.cpp + Interpreter.h + Interpreter.cpp +) + +add_library(yulInterpreter ${sources}) +target_link_libraries(yulInterpreter PUBLIC yul solidity devcore) diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp new file mode 100644 index 000000000..4b92edc18 --- /dev/null +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -0,0 +1,500 @@ +/* + 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 . +*/ +/** + * Yul interpreter module that evaluates EVM instructions. + */ + +#include + +#include + +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; +using namespace yul::test; + +namespace +{ + +u256 exp256(u256 _base, u256 _exponent) +{ + using boost::multiprecision::limb_type; + u256 result = 1; + while (_exponent) + { + if (static_cast(_exponent) & 1) // If exponent is odd. + result *= _base; + _base *= _base; + _exponent >>= 1; + } + return result; +} + +/// Reads 32 bytes from @a _data at position @a _offset bytes while +/// interpreting @a _data to be padded with an infinite number of zero +/// bytes beyond its end. +u256 readZeroExtended(bytes const& _data, u256 const& _offset) +{ + if (_offset >= _data.size()) + return 0; + else if (_offset + 32 <= _data.size()) + return *reinterpret_cast(_data.data() + size_t(_offset)); + else + { + size_t off = size_t(_offset); + u256 val; + for (size_t i = 0; i < 32; ++i) + { + val <<= 8; + if (off + i < _data.size()) + val += _data[off + i]; + } + return val; + } +} + +/// Copy @a _size bytes of @a _source at offset @a _sourceOffset to +/// @a _target at offset @a _targetOffset. Behaves as if @a _source would +/// continue with an infinite sequence of zero bytes beyond its end. +/// Asserts the target is large enough to hold the copied segment. +void copyZeroExtended( + bytes& _target, bytes const& _source, + size_t _targetOffset, size_t _sourceOffset, size_t _size +) +{ + yulAssert(_targetOffset + _size < _target.size(), ""); + for (size_t i = 0; i < _size; ++i) + _target[_targetOffset + i] = _sourceOffset + i < _source.size() ? _source[_sourceOffset + i] : 0; +} + +} + +using u512 = boost::multiprecision::number>; + +u256 EVMInstructionInterpreter::eval( + solidity::Instruction _instruction, + vector const& _arguments +) +{ + using dev::solidity::Instruction; + + auto info = solidity::instructionInfo(_instruction); + yulAssert(size_t(info.args) == _arguments.size(), ""); + + auto const& arg = _arguments; + switch (_instruction) + { + case Instruction::STOP: + throw InterpreterTerminated(); + // --------------- arithmetic --------------- + case Instruction::ADD: + return arg[0] + arg[1]; + case Instruction::MUL: + return arg[0] * arg[1]; + case Instruction::SUB: + return arg[0] - arg[1]; + case Instruction::DIV: + return arg[1] == 0 ? 0 : arg[0] / arg[1]; + case Instruction::SDIV: + return arg[1] == 0 ? 0 : s2u(u2s(arg[0]) / u2s(arg[1])); + case Instruction::MOD: + return arg[1] == 0 ? 0 : arg[0] % arg[1]; + case Instruction::SMOD: + return arg[1] == 0 ? 0 : s2u(u2s(arg[0]) % u2s(arg[1])); + case Instruction::EXP: + return exp256(arg[0], arg[1]); + case Instruction::NOT: + return ~arg[0]; + case Instruction::LT: + return arg[0] < arg[1] ? 1 : 0; + case Instruction::GT: + return arg[0] > arg[1] ? 1 : 0; + case Instruction::SLT: + return u2s(arg[0]) < u2s(arg[1]) ? 1 : 0; + case Instruction::SGT: + return u2s(arg[0]) > u2s(arg[1]) ? 1 : 0; + case Instruction::EQ: + return arg[0] == arg[1] ? 1 : 0; + case Instruction::ISZERO: + return arg[0] == 0 ? 1 : 0; + case Instruction::AND: + return arg[0] & arg[1]; + case Instruction::OR: + return arg[0] | arg[1]; + case Instruction::XOR: + return arg[0] ^ arg[1]; + case Instruction::BYTE: + return arg[0] >= 32 ? 0 : (arg[1] >> unsigned(8 * (31 - arg[0]))) & 0xff; + case Instruction::SHL: + return arg[0] > 255 ? 0 : (arg[1] << unsigned(arg[0])); + case Instruction::SHR: + return arg[0] > 255 ? 0 : (arg[1] >> unsigned(arg[0])); + case Instruction::SAR: + { + static u256 const hibit = u256(1) << 255; + if (arg[0] >= 256) + return arg[1] & hibit ? u256(-1) : 0; + else + { + unsigned amount = unsigned(arg[0]); + u256 v = arg[1] >> amount; + if (arg[1] & hibit) + v |= u256(-1) << (256 - amount); + return v; + } + } + case Instruction::ADDMOD: + return arg[2] == 0 ? 0 : u256((u512(arg[0]) + u512(arg[1])) % arg[2]); + case Instruction::MULMOD: + return arg[2] == 0 ? 0 : u256((u512(arg[0]) * u512(arg[1])) % arg[2]); + case Instruction::SIGNEXTEND: + if (arg[0] >= 31) + return arg[0]; + else + { + unsigned testBit = unsigned(arg[0]) * 8 + 7; + u256 ret = arg[1]; + u256 mask = ((u256(1) << testBit) - 1); + if (boost::multiprecision::bit_test(ret, testBit)) + ret |= ~mask; + else + ret &= mask; + return ret; + } + // --------------- blockchain stuff --------------- + case Instruction::KECCAK256: + { + if (!logMemoryRead(arg[0], arg[1])) + return u256("0x1234cafe1234cafe1234cafe") + arg[0]; + uint64_t offset = uint64_t(arg[0] & uint64_t(-1)); + uint64_t size = uint64_t(arg[1] & uint64_t(-1)); + return u256(keccak256(bytesConstRef(m_state.memory.data() + offset, size))); + } + case Instruction::ADDRESS: + return m_state.address; + case Instruction::BALANCE: + return m_state.balance; + case Instruction::ORIGIN: + return m_state.origin; + case Instruction::CALLER: + return m_state.caller; + case Instruction::CALLVALUE: + return m_state.callvalue; + case Instruction::CALLDATALOAD: + return readZeroExtended(m_state.calldata, arg[0]); + case Instruction::CALLDATASIZE: + return m_state.calldata.size(); + case Instruction::CALLDATACOPY: + if (logMemoryWrite(arg[0], arg[2])) + copyZeroExtended( + m_state.memory, m_state.calldata, + size_t(arg[0]), size_t(arg[1]), size_t(arg[2]) + ); + return 0; + case Instruction::CODESIZE: + return m_state.code.size(); + case Instruction::CODECOPY: + if (logMemoryWrite(arg[0], arg[2])) + copyZeroExtended( + m_state.memory, m_state.code, + size_t(arg[0]), size_t(arg[1]), size_t(arg[2]) + ); + return 0; + case Instruction::GASPRICE: + return m_state.gasprice; + case Instruction::EXTCODESIZE: + logTrace(_instruction, arg); + return u256(keccak256(h256(arg[0]))) & 0xffffff; + case Instruction::EXTCODEHASH: + logTrace(_instruction, arg); + return u256(keccak256(h256(arg[0] + 1))); + case Instruction::EXTCODECOPY: + logTrace(_instruction, arg); + if (logMemoryWrite(arg[1], arg[3])) + // TODO this way extcodecopy and codecopy do the same thing. + copyZeroExtended( + m_state.memory, m_state.code, + size_t(arg[1]), size_t(arg[2]), size_t(arg[3]) + ); + return 0; + case Instruction::RETURNDATASIZE: + logTrace(_instruction, arg); + return m_state.returndata.size(); + case Instruction::RETURNDATACOPY: + logTrace(_instruction, arg); + if (logMemoryWrite(arg[0], arg[2])) + copyZeroExtended( + m_state.memory, m_state.returndata, + size_t(arg[0]), size_t(arg[1]), size_t(arg[2]) + ); + return 0; + case Instruction::BLOCKHASH: + if (arg[0] >= m_state.blockNumber || arg[0] + 256 < m_state.blockNumber) + return 0; + else + return 0xaaaaaaaa + (arg[0] - m_state.blockNumber - 256); + case Instruction::COINBASE: + return m_state.coinbase; + case Instruction::TIMESTAMP: + return m_state.timestamp; + case Instruction::NUMBER: + return m_state.blockNumber; + case Instruction::DIFFICULTY: + return m_state.difficulty; + case Instruction::GASLIMIT: + return m_state.gaslimit; + // --------------- memory / storage / logs --------------- + case Instruction::MLOAD: + if (logMemoryRead(arg[0], 0x20)) + return u256(*reinterpret_cast(m_state.memory.data() + size_t(arg[0]))); + else + return 0x1234 + arg[0]; + case Instruction::MSTORE: + if (logMemoryWrite(arg[0], 0x20, h256(arg[1]).asBytes())) + *reinterpret_cast(m_state.memory.data() + size_t(arg[0])) = h256(arg[1]); + return 0; + case Instruction::MSTORE8: + if (logMemoryWrite(arg[0], 1, bytes{1, uint8_t(arg[1] & 0xff)})) + m_state.memory[size_t(arg[0])] = uint8_t(arg[1] & 0xff); + return 0; + case Instruction::SLOAD: + logTrace(_instruction, arg); + return m_state.storage[h256(arg[0])]; + case Instruction::SSTORE: + logTrace(Instruction::SSTORE, arg); + m_state.storage[h256(arg[0])] = h256(arg[1]); + return 0; + case Instruction::PC: + logTrace(_instruction); + return 0x77; + case Instruction::MSIZE: + logTrace(_instruction); + return m_state.msize; + case Instruction::GAS: + logTrace(_instruction); + return 0x99; + case Instruction::LOG0: + logMemoryRead(arg[0], arg[1]); + logTrace(_instruction, arg); + return 0; + case Instruction::LOG1: + logMemoryRead(arg[0], arg[1]); + logTrace(_instruction, arg); + return 0; + case Instruction::LOG2: + logMemoryRead(arg[0], arg[1]); + logTrace(_instruction, arg); + return 0; + case Instruction::LOG3: + logMemoryRead(arg[0], arg[1]); + logTrace(_instruction, arg); + return 0; + case Instruction::LOG4: + logMemoryRead(arg[0], arg[1]); + logTrace(_instruction, arg); + return 0; + // --------------- calls --------------- + case Instruction::CREATE: + logMemoryRead(arg[1], arg[2]); + logTrace(_instruction, arg); + return 0xcccccc + arg[1]; + case Instruction::CREATE2: + logMemoryRead(arg[2], arg[3]); + logTrace(_instruction, arg); + return 0xdddddd + arg[1]; + case Instruction::CALL: + case Instruction::CALLCODE: + // TODO assign returndata + logMemoryRead(arg[3], arg[4]); + logMemoryWrite(arg[5], arg[6]); + logTrace(_instruction, arg); + return arg[0] & 1; + case Instruction::DELEGATECALL: + case Instruction::STATICCALL: + logMemoryRead(arg[2], arg[3]); + logMemoryWrite(arg[4], arg[5]); + logTrace(_instruction, arg); + return 0; + case Instruction::RETURN: + { + bytes data; + if (logMemoryRead(arg[0], arg[1])) + data = bytesConstRef(m_state.memory.data() + size_t(arg[0]), size_t(arg[1])).toBytes(); + logTrace(_instruction, arg, data); + throw InterpreterTerminated(); + } + case Instruction::REVERT: + logMemoryRead(arg[0], arg[1]); + logTrace(_instruction, arg); + throw InterpreterTerminated(); + case Instruction::INVALID: + logTrace(_instruction); + throw InterpreterTerminated(); + case Instruction::SELFDESTRUCT: + logTrace(_instruction, arg); + throw InterpreterTerminated(); + case Instruction::POP: + break; + // --------------- invalid in strict assembly --------------- + case Instruction::JUMP: + case Instruction::JUMPI: + case Instruction::JUMPDEST: + case Instruction::PUSH1: + case Instruction::PUSH2: + case Instruction::PUSH3: + case Instruction::PUSH4: + case Instruction::PUSH5: + case Instruction::PUSH6: + case Instruction::PUSH7: + case Instruction::PUSH8: + case Instruction::PUSH9: + case Instruction::PUSH10: + case Instruction::PUSH11: + case Instruction::PUSH12: + case Instruction::PUSH13: + case Instruction::PUSH14: + case Instruction::PUSH15: + case Instruction::PUSH16: + case Instruction::PUSH17: + case Instruction::PUSH18: + case Instruction::PUSH19: + case Instruction::PUSH20: + case Instruction::PUSH21: + case Instruction::PUSH22: + case Instruction::PUSH23: + case Instruction::PUSH24: + case Instruction::PUSH25: + case Instruction::PUSH26: + case Instruction::PUSH27: + case Instruction::PUSH28: + case Instruction::PUSH29: + case Instruction::PUSH30: + case Instruction::PUSH31: + case Instruction::PUSH32: + case Instruction::DUP1: + case Instruction::DUP2: + case Instruction::DUP3: + case Instruction::DUP4: + case Instruction::DUP5: + case Instruction::DUP6: + case Instruction::DUP7: + case Instruction::DUP8: + case Instruction::DUP9: + case Instruction::DUP10: + case Instruction::DUP11: + case Instruction::DUP12: + case Instruction::DUP13: + case Instruction::DUP14: + case Instruction::DUP15: + case Instruction::DUP16: + case Instruction::SWAP1: + case Instruction::SWAP2: + case Instruction::SWAP3: + case Instruction::SWAP4: + case Instruction::SWAP5: + case Instruction::SWAP6: + case Instruction::SWAP7: + case Instruction::SWAP8: + case Instruction::SWAP9: + case Instruction::SWAP10: + case Instruction::SWAP11: + case Instruction::SWAP12: + case Instruction::SWAP13: + case Instruction::SWAP14: + case Instruction::SWAP15: + case Instruction::SWAP16: + // --------------- EVM 2.0 --------------- + case Instruction::JUMPTO: + case Instruction::JUMPIF: + case Instruction::JUMPV: + case Instruction::JUMPSUB: + case Instruction::JUMPSUBV: + case Instruction::BEGINSUB: + case Instruction::BEGINDATA: + case Instruction::RETURNSUB: + case Instruction::PUTLOCAL: + case Instruction::GETLOCAL: + { + yulAssert(false, ""); + return 0; + } + } + + return 0; +} + +bool EVMInstructionInterpreter::logMemoryRead(u256 const& _offset, u256 const& _size) +{ + return logMemory(false, _offset, _size); +} + +bool EVMInstructionInterpreter::logMemoryWrite(u256 const& _offset, u256 const& _size, bytes const& _data) +{ + return logMemory(true, _offset, _size, _data); +} + +bool EVMInstructionInterpreter::logMemory(bool _write, u256 const& _offset, u256 const& _size, bytes const& _data) +{ + /// Memory size limit. Anything beyond this will still work, but it has + /// deterministic yet not necessarily consistent behaviour. + size_t constexpr maxMemSize = 0x20000000; + + logTrace(_write ? "MSTORE_AT_SIZE" : "MLOAD_FROM_SIZE", {_offset, _size}, _data); + if (_offset + _size >= _offset) + { + u256 newSize = _offset + _size; + newSize = (newSize + 0x1f) & ~u256(0x1f); + m_state.msize = max(m_state.msize, newSize); + if (newSize < maxMemSize) + { + if (m_state.memory.size() < newSize) + m_state.memory.resize(size_t(newSize)); + return true; + } + } + else + m_state.msize = u256(-1); + + return false; +} + +void EVMInstructionInterpreter::logTrace(solidity::Instruction _instruction, std::vector const& _arguments, bytes const& _data) +{ + logTrace(solidity::instructionInfo(_instruction).name, _arguments, _data); +} + +void EVMInstructionInterpreter::logTrace(std::string const& _pseudoInstruction, std::vector const& _arguments, bytes const& _data) +{ + string message = _pseudoInstruction + "("; + for (size_t i = 0; i < _arguments.size(); ++i) + message += (i > 0 ? ", " : "") + formatNumber(_arguments[i]); + message += ")"; + if (!_data.empty()) + message += " [" + toHex(_data) + "]"; + m_state.trace.emplace_back(std::move(message)); + if (m_state.maxTraceSize > 0 && m_state.trace.size() >= m_state.maxTraceSize) + { + m_state.trace.emplace_back("Trace size limit reached."); + throw InterpreterTerminated(); + } +} diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.h b/test/tools/yulInterpreter/EVMInstructionInterpreter.h new file mode 100644 index 000000000..1ce6dbd9a --- /dev/null +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.h @@ -0,0 +1,90 @@ +/* + 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 . +*/ +/** + * Yul interpreter module that evaluates EVM instructions. + */ + +#pragma once + +#include + +#include + +#include + +namespace dev +{ +namespace solidity +{ +enum class Instruction: uint8_t; +} +} + +namespace yul +{ +namespace test +{ + +struct InterpreterState; + +/** + * Interprets EVM instructions based on the current state and logs instructions with + * side-effects. + * + * Since this is mainly meant to be used for differential fuzz testing, it is focused + * on a single contract only, does not do any gas counting and differs from the correct + * implementation in many ways: + * + * - If memory access to a "large" memory position is performed, a deterministic + * value is returned. Data that is stored in a "large" memory position is not + * retained. + * - The blockhash instruction returns a fixed value if the argument is in range. + * - Extcodesize returns a deterministic value depending on the address. + * - Extcodecopy copies a deterministic value depending on the address. + * - And many other things + * + * The main focus is that the generated execution trace is the same for equivalent executions + * and likely to be different for non-equivalent executions. + */ +class EVMInstructionInterpreter +{ +public: + explicit EVMInstructionInterpreter(InterpreterState& _state): + m_state(_state) + {} + dev::u256 eval(dev::solidity::Instruction _instruction, std::vector const& _arguments); + +private: + /// Record a memory read in the trace. Also updates m_state.msize + /// @returns true if m_state.memory can be used at that offset. + bool logMemoryRead(dev::u256 const& _offset, dev::u256 const& _size = 32); + /// Record a memory write in the trace. Also updates m_state.msize + /// @returns true if m_state.memory can be used at that offset. + bool logMemoryWrite(dev::u256 const& _offset, dev::u256 const& _size = 32, dev::bytes const& _data = {}); + + bool logMemory(bool _write, dev::u256 const& _offset, dev::u256 const& _size = 32, dev::bytes const& _data = {}); + + void logTrace(dev::solidity::Instruction _instruction, std::vector const& _arguments = {}, dev::bytes const& _data = {}); + /// Appends a log to the trace representing an instruction or similar operation by string, + /// with arguments and auxiliary data (if nonempty). + void logTrace(std::string const& _pseudoInstruction, std::vector const& _arguments = {}, dev::bytes const& _data = {}); + + InterpreterState& m_state; +}; + +} +} diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp new file mode 100644 index 000000000..c092da446 --- /dev/null +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -0,0 +1,235 @@ +/* + 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 . +*/ +/** + * Yul interpreter. + */ + +#include + +#include + +#include +#include + +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; +using namespace yul::test; + + +void Interpreter::operator()(ExpressionStatement const& _expressionStatement) +{ + evaluateMulti(_expressionStatement.expression); +} + +void Interpreter::operator()(Assignment const& _assignment) +{ + solAssert(_assignment.value, ""); + vector values = evaluateMulti(*_assignment.value); + solAssert(values.size() == _assignment.variableNames.size(), ""); + for (size_t i = 0; i < values.size(); ++i) + { + YulString varName = _assignment.variableNames.at(i).name; + solAssert(m_variables.count(varName), ""); + m_variables[varName] = values.at(i); + } +} + +void Interpreter::operator()(VariableDeclaration const& _declaration) +{ + vector values(_declaration.variables.size(), 0); + if (_declaration.value) + values = evaluateMulti(*_declaration.value); + + solAssert(values.size() == _declaration.variables.size(), ""); + for (size_t i = 0; i < values.size(); ++i) + { + YulString varName = _declaration.variables.at(i).name; + solAssert(!m_variables.count(varName), ""); + m_variables[varName] = values.at(i); + m_scopes.back().insert(varName); + } +} + +void Interpreter::operator()(If const& _if) +{ + solAssert(_if.condition, ""); + if (evaluate(*_if.condition) != 0) + (*this)(_if.body); +} + +void Interpreter::operator()(Switch const& _switch) +{ + solAssert(_switch.expression, ""); + u256 val = evaluate(*_switch.expression); + solAssert(!_switch.cases.empty(), ""); + for (auto const& c: _switch.cases) + // Default case has to be last. + if (!c.value || evaluate(*c.value) == val) + { + (*this)(c.body); + break; + } +} + +void Interpreter::operator()(FunctionDefinition const&) +{ +} + +void Interpreter::operator()(ForLoop const& _forLoop) +{ + solAssert(_forLoop.condition, ""); + + openScope(); + for (auto const& statement: _forLoop.pre.statements) + visit(statement); + while (evaluate(*_forLoop.condition) != 0) + { + (*this)(_forLoop.body); + (*this)(_forLoop.post); + } + closeScope(); +} + +void Interpreter::operator()(Block const& _block) +{ + openScope(); + // Register functions. + for (auto const& statement: _block.statements) + if (statement.type() == typeid(FunctionDefinition)) + { + FunctionDefinition const& funDef = boost::get(statement); + m_functions[funDef.name] = &funDef; + m_scopes.back().insert(funDef.name); + } + ASTWalker::operator()(_block); + closeScope(); +} + +u256 Interpreter::evaluate(Expression const& _expression) +{ + ExpressionEvaluator ev(m_state, m_variables, m_functions); + ev.visit(_expression); + return ev.value(); +} + +vector Interpreter::evaluateMulti(Expression const& _expression) +{ + ExpressionEvaluator ev(m_state, m_variables, m_functions); + ev.visit(_expression); + return ev.values(); +} + +void Interpreter::closeScope() +{ + for (auto const& var: m_scopes.back()) + { + size_t erased = m_variables.erase(var) + m_functions.erase(var); + solAssert(erased == 1, ""); + } + m_scopes.pop_back(); +} + +void ExpressionEvaluator::operator()(Literal const& _literal) +{ + static YulString const trueString("true"); + static YulString const falseString("false"); + + switch (_literal.kind) + { + case LiteralKind::Boolean: + solAssert(_literal.value == trueString || _literal.value == falseString, ""); + setValue(_literal.value == trueString ? 1 : 0); + break; + case LiteralKind::Number: + setValue(valueOfNumberLiteral(_literal)); + break; + case LiteralKind::String: + solAssert(_literal.value.str().size() <= 32, ""); + setValue(u256(h256(_literal.value.str(), h256::FromBinary, h256::AlignLeft))); + break; + } +} + +void ExpressionEvaluator::operator()(Identifier const& _identifier) +{ + solAssert(m_variables.count(_identifier.name), ""); + setValue(m_variables.at(_identifier.name)); +} + +void ExpressionEvaluator::operator()(FunctionalInstruction const& _instr) +{ + evaluateArgs(_instr.arguments); + EVMInstructionInterpreter interpreter(m_state); + // The instruction might also return nothing, but it does not + // hurt to set the value in that case. + setValue(interpreter.eval(_instr.instruction, values())); +} + +void ExpressionEvaluator::operator()(FunctionCall const& _funCall) +{ + solAssert(m_functions.count(_funCall.functionName.name), ""); + evaluateArgs(_funCall.arguments); + + FunctionDefinition const& fun = *m_functions.at(_funCall.functionName.name); + solAssert(m_values.size() == fun.parameters.size(), ""); + map variables; + for (size_t i = 0; i < fun.parameters.size(); ++i) + variables[fun.parameters.at(i).name] = m_values.at(i); + for (size_t i = 0; i < fun.returnVariables.size(); ++i) + variables[fun.returnVariables.at(i).name] = 0; + + // TODO function name lookup could be a little more efficient, + // we have to copy the list here. + Interpreter interpreter(m_state, variables, m_functions); + interpreter(fun.body); + + m_values.clear(); + for (auto const& retVar: fun.returnVariables) + m_values.emplace_back(interpreter.valueOfVariable(retVar.name)); +} + +u256 ExpressionEvaluator::value() const +{ + solAssert(m_values.size() == 1, ""); + return m_values.front(); +} + +void ExpressionEvaluator::setValue(u256 _value) +{ + m_values.clear(); + m_values.emplace_back(std::move(_value)); +} + +void ExpressionEvaluator::evaluateArgs(vector const& _expr) +{ + vector values; + /// Function arguments are evaluated in reverse. + for (auto const& expr: _expr | boost::adaptors::reversed) + { + visit(expr); + values.push_back(value()); + } + m_values = std::move(values); + std::reverse(m_values.begin(), m_values.end()); +} diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h new file mode 100644 index 000000000..267fe0a4d --- /dev/null +++ b/test/tools/yulInterpreter/Interpreter.h @@ -0,0 +1,162 @@ +/* + 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 . +*/ +/** + * Yul interpreter. + */ + +#pragma once + +#include +#include + +#include +#include + +#include + +#include + +namespace yul +{ +namespace test +{ + +class InterpreterTerminated: dev::Exception +{ +}; + +struct InterpreterState +{ + dev::bytes calldata; + dev::bytes returndata; + /// TODO turn this into "vector with holes" for the randomized testing + dev::bytes memory; + /// This is different than memory.size() because we ignore gas. + dev::u256 msize; + std::map storage; + dev::u160 address = 0x11111111; + dev::u256 balance = 0x22222222; + dev::u160 origin = 0x33333333; + dev::u160 caller = 0x44444444; + dev::u256 callvalue = 0x55555555; + /// Deployed code + dev::bytes code = dev::asBytes("codecodecodecodecode"); + dev::u256 gasprice = 0x66666666; + dev::u160 coinbase = 0x77777777; + dev::u256 timestamp = 0x88888888; + dev::u256 blockNumber = 1024; + dev::u256 difficulty = 0x9999999; + dev::u256 gaslimit = 4000000; + /// Log of changes / effects. Sholud be structured data in the future. + std::vector trace; + /// This is actually an input parameter that more or less limits the runtime. + size_t maxTraceSize = 0; +}; + +/** + * Yul interpreter. + */ +class Interpreter: public ASTWalker +{ +public: + Interpreter( + InterpreterState& _state, + std::map _variables = {}, + std::map _functions = {} + ): + m_state(_state), + m_variables(std::move(_variables)), + m_functions(std::move(_functions)) + {} + + void operator()(ExpressionStatement const& _statement) override; + void operator()(Assignment const& _assignment) override; + void operator()(VariableDeclaration const& _varDecl) override; + void operator()(If const& _if) override; + void operator()(Switch const& _switch) override; + void operator()(FunctionDefinition const&) override; + void operator()(ForLoop const&) override; + void operator()(Block const& _block) override; + + std::vector const& trace() const { return m_state.trace; } + + dev::u256 valueOfVariable(YulString _name) const { return m_variables.at(_name); } + +private: + /// Asserts that the expression evaluates to exactly one value and returns it. + dev::u256 evaluate(Expression const& _expression); + /// Evaluates the expression and returns its value. + std::vector evaluateMulti(Expression const& _expression); + + void openScope() { m_scopes.push_back({}); } + /// Unregisters variables. + void closeScope(); + + InterpreterState& m_state; + /// Values of variables. + std::map m_variables; + /// Meanings of functions. + std::map m_functions; + /// Scopes of variables and functions, used to clear them at end of blocks. + std::vector> m_scopes; +}; + +/** + * Yul expression evaluator. + */ +class ExpressionEvaluator: public ASTWalker +{ +public: + ExpressionEvaluator( + InterpreterState& _state, + std::map const& _variables, + std::map const& _functions + ): + m_state(_state), + m_variables(_variables), + m_functions(_functions) + {} + + void operator()(Literal const&) override; + void operator()(Identifier const&) override; + void operator()(FunctionalInstruction const& _instr) override; + void operator()(FunctionCall const& _funCall) override; + + /// Asserts that the expression has exactly one value and returns it. + dev::u256 value() const; + /// Returns the list of values of the expression. + std::vector values() const { return m_values; } + +private: + void setValue(dev::u256 _value); + + /// Evaluates the given expression from right to left and + /// stores it in m_value. + void evaluateArgs(std::vector const& _expr); + + InterpreterState& m_state; + /// Values of variables. + std::map const& m_variables; + /// Meanings of functions. + std::map const& m_functions; + /// Current value of the expression + std::vector m_values; +}; + +} +} + diff --git a/test/tools/yulrun.cpp b/test/tools/yulrun.cpp new file mode 100644 index 000000000..3e02a55df --- /dev/null +++ b/test/tools/yulrun.cpp @@ -0,0 +1,144 @@ +/* + 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 . +*/ +/** + * Yul interpreter. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +using namespace std; +using namespace langutil; +using namespace yul; +using namespace dev; +using namespace yul::test; + +namespace po = boost::program_options; + +namespace +{ + +void printErrors(ErrorList const& _errors) +{ + for (auto const& error: _errors) + SourceReferenceFormatter(cout).printExceptionInformation( + *error, + (error->type() == Error::Type::Warning) ? "Warning" : "Error" + ); +} + +pair, shared_ptr> parse(string const& _source) +{ + AssemblyStack stack(dev::solidity::EVMVersion(), AssemblyStack::Language::StrictAssembly); + if (stack.parseAndAnalyze("--INPUT--", _source)) + { + yulAssert(stack.errors().empty(), "Parsed successfully but had errors."); + return make_pair(stack.parserResult()->code, stack.parserResult()->analysisInfo); + } + else + { + printErrors(stack.errors()); + return {}; + } +} + +void interpret() +{ + string source = readStandardInput(); + shared_ptr ast; + shared_ptr analysisInfo; + tie(ast, analysisInfo) = parse(source); + if (!ast || !analysisInfo) + return; + + InterpreterState state; + state.maxTraceSize = 10000; + Interpreter interpreter(state); + try + { + interpreter(*ast); + } + catch (InterpreterTerminated const&) + { + } + + cout << "Trace:" << endl; + for (auto const& line: interpreter.trace()) + cout << " " << line << endl; + cout << "Memory dump:" << endl; + for (size_t i = 0; i < state.memory.size(); i += 0x20) + cout << " " << std::hex << std::setw(4) << i << ": " << toHex(bytesConstRef(state.memory.data() + i, 0x20).toBytes()) << endl; + cout << "Storage dump:" << endl; + for (auto const& slot: state.storage) + cout << " " << slot.first.hex() << ": " << slot.second.hex() << endl; +} + +} + +int main(int argc, char** argv) +{ + po::options_description options( + R"(yulrun, the Yul interpreter. +Usage: yulrun [Options] < input +Reads a single source from stdin, runs it and prints a trace of all side-effects. + +Allowed options)", + po::options_description::m_default_line_length, + po::options_description::m_default_line_length - 23); + options.add_options() + ("help", "Show this help screen."); + + po::variables_map arguments; + try + { + po::command_line_parser cmdLineParser(argc, argv); + cmdLineParser.options(options); + po::store(cmdLineParser.run(), arguments); + } + catch (po::error const& _exception) + { + cerr << _exception.what() << endl; + return 1; + } + + if (arguments.count("help")) + cout << options; + else + interpret(); + + return 0; +}