Add fuzzer for testing clean up of dirty bits via inline assembly

This commit is contained in:
Bhargava Shastry 2020-05-09 12:46:05 +02:00
parent 4e58c672bb
commit 7c91a31cce
22 changed files with 1023 additions and 1189 deletions

View File

@ -96,6 +96,7 @@ defaults:
- test/tools/ossfuzz/strictasm_diff_ossfuzz
- test/tools/ossfuzz/strictasm_opt_ossfuzz
- test/tools/ossfuzz/yul_proto_diff_ossfuzz
- test/tools/ossfuzz/yul_proto_diff_custom_mutate_ossfuzz
- test/tools/ossfuzz/yul_proto_ossfuzz
# test result output directory

View File

@ -37,6 +37,8 @@ namespace solidity::langutil
class Error;
using ErrorList = std::vector<std::shared_ptr<Error const>>;
struct FuzzerError: virtual util::Exception {};
struct StackTooDeepError: virtual util::Exception {};
struct CompilerError: virtual util::Exception {};
struct InternalCompilerError: virtual util::Exception {};
struct FatalError: virtual util::Exception {};

View File

@ -219,8 +219,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
else
solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
solAssert(
assertThrow(
2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
StackTooDeepError,
"Stack too deep, try removing local variables."
);
// fetch target storage reference

View File

@ -405,7 +405,7 @@ void CompilerContext::appendInlineAssembly(
stackDiff -= 1;
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_identifier.location) <<
util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
);

View File

@ -455,7 +455,11 @@ void CompilerUtils::encodeToMemory(
// leave end_of_mem as dyn head pointer
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
dynPointers++;
solAssert((argSize + dynPointers) < 16, "Stack too deep, try using fewer variables.");
assertThrow(
(argSize + dynPointers) < 16,
StackTooDeepError,
"Stack too deep, try using fewer variables."
);
}
else
{
@ -497,8 +501,9 @@ void CompilerUtils::encodeToMemory(
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
{
// copy tail pointer (=mem_end - mem_start) to memory
solAssert(
assertThrow(
(2 + dynPointers) <= 16,
StackTooDeepError,
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
);
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
@ -1241,7 +1246,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
// move variable starting from its top end in the stack
if (stackPosition - size + 1 > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_variable.location()) <<
util::errinfo_comment("Stack too deep, try removing local variables.")
);
@ -1251,7 +1256,11 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
{
solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables.");
assertThrow(
_stackDepth <= 16,
StackTooDeepError,
"Stack too deep, try removing local variables."
);
for (unsigned i = 0; i < _itemSize; ++i)
m_context << dupInstruction(_stackDepth);
}
@ -1273,14 +1282,22 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize)
void CompilerUtils::rotateStackUp(unsigned _items)
{
solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables.");
assertThrow(
_items - 1 <= 16,
StackTooDeepError,
"Stack too deep, try removing local variables."
);
for (unsigned i = 1; i < _items; ++i)
m_context << swapInstruction(_items - i);
}
void CompilerUtils::rotateStackDown(unsigned _items)
{
solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables.");
assertThrow(
_items - 1 <= 16,
StackTooDeepError,
"Stack too deep, try removing local variables."
);
for (unsigned i = 1; i < _items; ++i)
m_context << swapInstruction(i);
}

View File

@ -631,7 +631,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
if (stackLayout.size() > 17)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_function.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
@ -795,7 +795,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
solAssert(variable->type()->sizeOnStack() == 1, "");
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
@ -828,7 +828,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable) - 1;
if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
);

View File

@ -226,7 +226,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), "");
if (retSizeOnStack > 15)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_varDecl.location()) <<
errinfo_comment("Stack too deep.")
);
@ -308,7 +308,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
{
if (itemSize + lvalueSize > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_assignment.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);

View File

@ -47,7 +47,7 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_location) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
@ -61,7 +61,7 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1;
if (stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_location) <<
errinfo_comment("Stack too deep, try removing local variables.")
);

View File

@ -218,10 +218,10 @@ void CodeGenerator::assemble(
}
catch (StackTooDeepError const& _e)
{
yulAssert(
assertThrow(
false,
"Stack too deep when compiling inline assembly" +
(_e.comment() ? ": " + *_e.comment() : ".")
langutil::StackTooDeepError,
""
);
}
yulAssert(transform.stackErrors().empty(), "Stack errors present but not thrown.");

View File

@ -10,7 +10,12 @@ add_dependencies(ossfuzz
if (OSSFUZZ)
add_custom_target(ossfuzz_proto)
add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz)
add_dependencies(ossfuzz_proto
yul_proto_ossfuzz
yul_proto_diff_ossfuzz
yul_proto_diff_custom_mutate_ossfuzz
sol_arith_proto_ossfuzz
)
add_custom_target(ossfuzz_abiv2)
add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz)
@ -60,6 +65,22 @@ if (OSSFUZZ)
)
set_target_properties(yul_proto_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
add_executable(yul_proto_diff_custom_mutate_ossfuzz
yulProto_diff_ossfuzz.cpp
yulFuzzerCommon.cpp
protoToYul.cpp
yulProto.pb.cc
protomutators/YulProtoMutator.cpp
)
target_include_directories(yul_proto_diff_custom_mutate_ossfuzz PRIVATE /usr/include/libprotobuf-mutator)
target_link_libraries(yul_proto_diff_custom_mutate_ossfuzz PRIVATE yul
yulInterpreter
protobuf-mutator-libfuzzer.a
protobuf-mutator.a
protobuf.a
)
set_target_properties(yul_proto_diff_custom_mutate_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
add_executable(abiv2_proto_ossfuzz
../../EVMHost.cpp
abiV2ProtoFuzzer.cpp
@ -78,6 +99,52 @@ if (OSSFUZZ)
protobuf.a
)
set_target_properties(abiv2_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
add_executable(sol_arith_proto_ossfuzz
solArithProtoFuzzer.cpp
solarithprotoToSol.cpp
solArith.pb.cc
abiV2FuzzerCommon.cpp
../../EVMHost.cpp
protoToYul.cpp
yulProto.pb.cc
yulFuzzerCommon.cpp
)
target_include_directories(sol_arith_proto_ossfuzz PRIVATE
/usr/include/libprotobuf-mutator
)
target_link_libraries(sol_arith_proto_ossfuzz PRIVATE solidity libsolc
yulInterpreter
evmc
evmone-standalone
protobuf-mutator-libfuzzer.a
protobuf-mutator.a
protobuf.a
)
set_target_properties(sol_arith_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
# add_executable(sol_proto_ossfuzz
# solProtoFuzzer.cpp
# protoToSol.cpp
# solProto.pb.cc
# abiV2FuzzerCommon.cpp
# ../../EVMHost.cpp
# SolProtoAdaptor.cpp
# )
# target_include_directories(sol_proto_ossfuzz PRIVATE
# /usr/include/libprotobuf-mutator
# /home/bhargava/work/github/solidity/deps/LPM/external.protobuf/include
# /home/bhargava/work/github/solidity/deps/evmone/include
# /home/bhargava/work/github/solidity/deps/libprotobuf-mutator
# )
# target_link_libraries(sol_proto_ossfuzz PRIVATE solidity libsolc
# evmc
# evmone-standalone
# protobuf-mutator-libfuzzer.a
# protobuf-mutator.a
# protobuf.a
# )
# set_target_properties(sol_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
else()
add_library(solc_opt_ossfuzz
solc_opt_ossfuzz.cpp

File diff suppressed because it is too large Load Diff

View File

@ -38,10 +38,14 @@ namespace solidity::yul::test::yul_fuzzer
class ProtoConverter
{
public:
ProtoConverter()
ProtoConverter(unsigned _numGlobalVars)
{
m_funcVars = std::vector<std::vector<std::vector<std::string>>>{};
m_globalVars = std::vector<std::vector<std::string>>{};
if (_numGlobalVars > 0)
m_globalVars.push_back({});
for (unsigned i = 0; i < _numGlobalVars; i++)
m_globalVars.back().push_back("v" + std::to_string(i));
m_inForBodyScope = false;
m_inForInitScope = false;
m_inForCond = false;
@ -50,20 +54,12 @@ public:
m_counter = 0;
m_inputSize = 0;
m_inFunctionDef = false;
m_objectId = 0;
m_isObject = false;
m_forInitScopeExtEnabled = true;
}
ProtoConverter(ProtoConverter const&) = delete;
ProtoConverter(ProtoConverter&&) = delete;
std::string programToString(Program const& _input);
/// Returns evm version
solidity::langutil::EVMVersion version()
{
return m_evmVersion;
}
private:
void visit(BinaryOp const&);
@ -75,10 +71,8 @@ private:
std::string visit(Literal const&);
void visit(VarRef const&);
void visit(FunctionExpr const& _x);
void visit(Expression const&);
void visit(VarDecl const&);
void visit(MultiVarDecl const&);
void visit(TypedVarDecl const&);
void visit(UnaryOp const&);
void visit(AssignmentStatement const&);
void visit(IfStmt const&);
@ -90,23 +84,10 @@ private:
void visit(SwitchStmt const&);
void visit(TernaryOp const&);
void visit(NullaryOp const&);
void visit(LogFunc const&);
void visit(CopyFunc const&);
void visit(ExtCodeCopy const&);
void visit(StopInvalidStmt const&);
void visit(RetRevStmt const&);
void visit(SelfDestructStmt const&);
void visit(TerminatingStmt const&);
void visit(FunctionCall const&);
void visit(FunctionDef const&);
void visit(PopStmt const&);
void visit(LeaveStmt const&);
void visit(LowLevelCall const&);
void visit(Create const&);
void visit(UnaryOpData const&);
void visit(Object const&);
void visit(Data const&);
void visit(Code const&);
void visit(Program const&);
/// Creates a new block scope.
@ -140,8 +121,10 @@ private:
Multiple
};
void visitFunctionInputParams(FunctionCall const&, unsigned);
template <typename T>
void visitFunctionInputParams(T const&, unsigned);
void createFunctionDefAndCall(FunctionDef const&, unsigned, unsigned);
void storeGlobals();
/// Convert function type to a string to be used while naming a
/// function that is created by a function declaration statement.
@ -169,46 +152,19 @@ private:
/// in scope
bool varDeclAvailable();
/// Return true if a function call cannot be made, false otherwise.
/// @param _type is an enum denoting the type of function call. It
/// can be one of NONE, SINGLE, MULTIDECL, MULTIASSIGN.
/// NONE -> Function call does not return a value
/// SINGLE -> Function call returns a single value
/// MULTIDECL -> Function call returns more than one value
/// and it is used to create a multi declaration
/// statement
/// MULTIASSIGN -> Function call returns more than one value
/// and it is used to create a multi assignment
/// statement
/// @return True if the function call cannot be created for one of the
/// following reasons
// - It is a SINGLE function call (we reserve SINGLE functions for
// expressions)
// - It is a MULTIASSIGN function call and we do not have any
// variables available for assignment.
bool functionCallNotPossible(FunctionCall_Returns _type);
/// Checks if function call of type @a _type returns the correct number
/// of values.
/// @param _type Function call type of the function being checked
/// @param _numOutParams Number of values returned by the function
/// being checked
/// @return true if the function returns the correct number of values,
/// false otherwise
bool functionValid(FunctionCall_Returns _type, unsigned _numOutParams);
/// Converts protobuf function call to a Yul function call and appends
/// Converts protobuf function call to a yul function call and appends
/// it to output stream.
/// @param _x Protobuf function call
/// @param _name Function name
/// @param _numInParams Number of input arguments accepted by function
/// @param _newLine Flag that prints a new line to the output stream if
/// true. Default value for the flag is true.
/// @param _newline Boolean flag that is true if new line to be printed after
/// function call, false otherwise
template <typename T>
void convertFunctionCall(
FunctionCall const& _x,
T const& _x,
std::string _name,
unsigned _numInParams,
bool _newLine = true
bool _newline = false
);
/// Prints a Yul formatted variable declaration statement to the output
@ -270,11 +226,6 @@ private:
/// Removes entry from m_functionMap and m_functionName
void updateFunctionMaps(std::string const& _x);
/// Build a tree of objects that contains the object/data
/// identifiers that are in scope in a given object.
/// @param _x root object of the Yul protobuf specification.
void buildObjectScopeTree(Object const& _x);
/// Returns a pseudo-random dictionary token.
/// @param _p Enum that decides if the returned token is hex prefixed ("0x") or not
/// @return Dictionary token at the index computed using a
@ -284,9 +235,9 @@ private:
/// dictionarySize is the total number of entries in the dictionary.
std::string dictionaryToken(util::HexPrefix _p = util::HexPrefix::Add);
/// Returns an EVMVersion object corresponding to the protobuf
/// enum of type Program_Version
solidity::langutil::EVMVersion evmVersionMapping(Program_Version const& _x);
/// Return variable reference.
/// @param _index: Index of variable to be referenced
std::string varRef(unsigned _index);
/// Returns a monotonically increasing counter that starts from zero.
unsigned counter()
@ -302,29 +253,7 @@ private:
return "foo_" + functionTypeToString(_type) + "_" + std::to_string(counter());
}
/// Returns a pseudo-randomly chosen object identifier that is in the
/// scope of the Yul object being visited.
std::string getObjectIdentifier(unsigned _x);
/// Return new object identifier as string. Identifier string
/// is a template of the form "\"object<n>\"" where <n> is
/// a monotonically increasing object ID counter.
/// @param _decorate If true (default value), object ID is
/// enclosed within double quotes.
std::string newObjectId(bool _decorate = true)
{
return util::Whiskers(R"(<?decorate>"</decorate>object<id><?decorate>"</decorate>)")
("decorate", _decorate)
("id", std::to_string(m_objectId++))
.render();
}
/// Returns the object counter value corresponding to the object
/// being visited.
unsigned currentObjectId()
{
return m_objectId - 1;
}
std::string dummyExpression();
std::ostringstream m_output;
/// Variables in all function definitions
@ -349,13 +278,9 @@ private:
std::stack<std::set<u256>> m_switchLiteralSetPerScope;
// Look-up table per function type that holds the number of input (output) function parameters
std::map<std::string, std::pair<unsigned, unsigned>> m_functionSigMap;
/// Tree of objects and their scopes
std::vector<std::vector<std::string>> m_objectScopeTree;
// mod input/output parameters impose an upper bound on the number of input/output parameters a function may have.
static unsigned constexpr s_modInputParams = 5;
static unsigned constexpr s_modOutputParams = 5;
/// Hard-coded identifier for a Yul object's data block
static auto constexpr s_dataIdentifier = "datablock";
/// Predicate to keep track of for body scope. If false, break/continue
/// statements can not be created.
bool m_inForBodyScope;
@ -368,24 +293,15 @@ private:
/// Predicate to keep track of for loop init scope. If true, variable
/// or function declarations can not be created.
bool m_inForInitScope;
/// Flag that is true while converting for loop condition,
/// false otherwise.
bool m_inForCond;
/// Monotonically increasing counter
unsigned m_counter;
/// Size of protobuf input
unsigned m_inputSize;
/// Predicate that is true if inside function definition, false otherwise
bool m_inFunctionDef;
/// Index used for naming objects
unsigned m_objectId;
/// Flag to track whether program is an object (true) or a statement block
/// (false: default value)
bool m_isObject;
/// Flag to track whether scope extension of variables defined in for-init
/// block is enabled.
bool m_forInitScopeExtEnabled;
/// Object that holds the targeted evm version specified by protobuf input
solidity::langutil::EVMVersion m_evmVersion;
bool m_inForCond;
};
}

View File

@ -0,0 +1,49 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
syntax = "proto2";
import "yulProto.proto";
message Type {
enum Sign {
SIGNED = 0;
UNSIGNED = 1;
}
required Sign s = 1;
required uint32 bytewidth = 2;
}
message VarDecl {
required Type t = 1;
}
message Assembly {
required solidity.yul.test.yul_fuzzer.Program p = 1;
}
message Block {
repeated VarDecl v = 1;
required Assembly a = 2;
}
message Program {
required Block b = 1;
required uint64 seed = 2;
}
package solidity.test.solarithfuzzer;

View File

@ -0,0 +1,268 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
#include <test/tools/ossfuzz/solarithprotoToSol.h>
#include <test/tools/ossfuzz/solArith.pb.h>
#include <test/tools/ossfuzz/abiV2FuzzerCommon.h>
#include <test/EVMHost.h>
#include <evmone/evmone.h>
#include <src/libfuzzer/libfuzzer_macro.h>
#include <fstream>
static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
using namespace solidity::test::abiv2fuzzer;
using namespace solidity::test::solarithfuzzer;
using namespace solidity;
using namespace solidity::test;
using namespace solidity::util;
using namespace std;
namespace
{
/// Test function returns a uint256 value
//static size_t const expectedOutputLength = 32;
///// Expected output value is decimal 0
//static uint8_t const expectedOutput[expectedOutputLength] = {
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
//};
/// Compares the contents of the memory address pointed to
/// by `_result` of `_length` bytes to the expected output.
/// Returns true if `_result` matches expected output, false
/// otherwise.
//bool isOutputExpected(evmc::result const& _run)
//{
// if (_run.output_size != expectedOutputLength)
// return false;
//
// return (memcmp(_run.output_data, expectedOutput, expectedOutputLength) == 0);
//}
bool compareRuns(evmc::result const& _run1, evmc::result const& _run2)
{
if (_run1.output_size != _run2.output_size)
return false;
return memcmp(_run1.output_data, _run2.output_data, _run1.output_size) == 0;
}
/// Accepts a reference to a user-specified input and returns an
/// evmc_message with all of its fields zero initialized except
/// gas and input fields.
/// The gas field is set to the maximum permissible value so that we
/// don't run into out of gas errors. The input field is copied from
/// user input.
evmc_message initializeMessage(bytes const& _input)
{
// Zero initialize all message fields
evmc_message msg = {};
// Gas available (value of type int64_t) is set to its maximum
// value.
msg.gas = std::numeric_limits<int64_t>::max();
msg.input_data = _input.data();
msg.input_size = _input.size();
return msg;
}
/// Accepts host context implementation, and keccak256 hash of the function
/// to be called at a specified address in the simulated blockchain as
/// input and returns the result of the execution of the called function.
evmc::result executeContract(
EVMHost& _hostContext,
bytes const& _functionHash,
evmc_address _deployedAddress
)
{
evmc_message message = initializeMessage(_functionHash);
message.destination = _deployedAddress;
message.kind = EVMC_CALL;
return _hostContext.call(message);
}
/// Accepts a reference to host context implementation and byte code
/// as input and deploys it on the simulated blockchain. Returns the
/// result of deployment.
evmc::result deployContract(EVMHost& _hostContext, bytes const& _code)
{
evmc_message message = initializeMessage(_code);
message.kind = EVMC_CREATE;
return _hostContext.call(message);
}
std::pair<bytes, Json::Value> compileContract(
std::string _sourceCode,
std::string _contractName,
std::map<std::string, solidity::util::h160> const& _libraryAddresses = {},
frontend::OptimiserSettings _optimization = frontend::OptimiserSettings::minimal()
)
{
try
{
// Compile contract generated by the proto fuzzer
SolidityCompilationFramework solCompilationFramework;
return std::make_pair(
solCompilationFramework.compileContract(_sourceCode, _contractName, _libraryAddresses, _optimization),
solCompilationFramework.getMethodIdentifiers()
);
}
// Ignore stack too deep errors during compilation
catch (langutil::StackTooDeepError const&)
{
throw langutil::FuzzerError();
}
}
evmc::result deployAndExecute(EVMHost& _hostContext, bytes _byteCode, std::string _hexEncodedInput)
{
// Deploy contract and signal failure if deploy failed
evmc::result createResult = deployContract(_hostContext, _byteCode);
solAssert(
createResult.status_code == EVMC_SUCCESS,
"Proto solc fuzzer: Contract creation failed"
);
// Execute test function and signal failure if EVM reverted or
// did not return expected output on successful execution.
evmc::result callResult = executeContract(
_hostContext,
fromHex(_hexEncodedInput),
createResult.create_address
);
// We don't care about EVM One failures other than EVMC_REVERT
solAssert(callResult.status_code != EVMC_REVERT, "Proto solc fuzzer: EVM One reverted");
return callResult;
}
evmc::result compileDeployAndExecute(
std::string _sourceCode,
std::string _contractName,
std::string _methodName,
frontend::OptimiserSettings _optimization,
std::string _libraryName = {}
)
{
bytes libraryBytecode;
Json::Value libIds;
// We target the default EVM which is the latest
langutil::EVMVersion version = {};
EVMHost hostContext(version, evmone);
std::map<std::string, solidity::util::h160> _libraryAddressMap;
// First deploy library
if (!_libraryName.empty())
{
tie(libraryBytecode, libIds) = compileContract(
_sourceCode,
_libraryName,
{},
_optimization
);
// Deploy contract and signal failure if deploy failed
evmc::result createResult = deployContract(hostContext, libraryBytecode);
solAssert(
createResult.status_code == EVMC_SUCCESS,
"Proto solc fuzzer: Library deployment failed"
);
_libraryAddressMap[_libraryName] = EVMHost::convertFromEVMC(createResult.create_address);
}
auto [bytecode, ids] = compileContract(
_sourceCode,
_contractName,
_libraryAddressMap,
_optimization
);
return deployAndExecute(
hostContext,
bytecode,
ids[_methodName].asString()
);
}
}
DEFINE_PROTO_FUZZER(Program const& _input)
{
ProtoConverter converter;
string sol_source{};
try {
sol_source = converter.programToString(_input);
}
catch (langutil::FuzzerError const&)
{
return;
}
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
{
// With libFuzzer binary run this to generate a YUL source file x.yul:
// PROTO_FUZZER_DUMP_PATH=x.yul ./a.out proto-input
ofstream of(dump_path);
of.write(sol_source.data(), sol_source.size());
}
if (const char* dump_path = getenv("SOL_DEBUG_FILE"))
{
sol_source.clear();
// With libFuzzer binary run this to generate a YUL source file x.yul:
// PROTO_FUZZER_LOAD_PATH=x.yul ./a.out proto-input
ifstream ifstr(dump_path);
sol_source = {
std::istreambuf_iterator<char>(ifstr),
std::istreambuf_iterator<char>()
};
std::cout << sol_source << std::endl;
}
try {
auto minimalResult = compileDeployAndExecute(
sol_source,
":C",
"test()",
frontend::OptimiserSettings::minimal(),
""
);
bool successState = minimalResult.status_code == EVMC_SUCCESS;
auto optResult = compileDeployAndExecute(
sol_source,
":C",
"test()",
frontend::OptimiserSettings::standard(),
""
);
if (successState)
{
solAssert(
optResult.status_code == EVMC_SUCCESS,
"Sol arith fuzzer: Optimal code failed"
);
solAssert(
compareRuns(minimalResult, optResult),
"Sol arith fuzzer: Runs produced different result"
);
}
}
catch (langutil::FuzzerError const&)
{
}
}

View File

@ -0,0 +1,180 @@
#include <test/tools/ossfuzz/solarithprotoToSol.h>
#include <test/tools/ossfuzz/protoToYul.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/Whiskers.h>
#include <libyul/AssemblyStack.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/Exceptions.h>
using namespace solidity::test::solarithfuzzer;
using namespace solidity;
using namespace solidity::util;
using namespace std;
string ProtoConverter::programToString(Program const& _program)
{
m_rand = make_unique<SolRandomNumGenerator>(
SolRandomNumGenerator(_program.seed())
);
return visit(_program);
}
string ProtoConverter::visit(Program const& _program)
{
Whiskers p(R"(pragma solidity >= 0.0.0;)");
Whiskers c(R"(contract C {)");
Whiskers t(R"(function test() public returns (uint)<body>)");
t("body", visit(_program.b()));
return p.render()
+ '\n'
+ c.render()
+ '\n'
+ '\t'
+ t.render()
+ '\n'
+ '}';
}
string ProtoConverter::visit(Block const& _block)
{
ostringstream blockStr;
blockStr << '\n'
<< '\t'
<< '{'
<< '\n';
for (auto const& v: _block.v())
blockStr << visit(v);
blockStr << visit(_block.a());
ostringstream trace;
if (m_yulAssembly.empty())
blockStr << "\t\treturn 0;";
else
blockStr << addChecks(m_yulProgram, langutil::EVMVersion::berlin(), trace);
blockStr << '\n'
<< '\t'
<< '}';
return blockStr.str();
}
string ProtoConverter::addChecks(
string const& _yulSource,
langutil::EVMVersion _version,
ostringstream& _os
)
{
ostringstream out;
unsigned error = 1;
auto memoryDump = interpretYul(_yulSource, _version, _os);
unsigned index = 0;
for (auto const& v: m_varTypeMap)
{
Whiskers check(R"(<ind>if (<var> != <type>(0x<value>)) return <error>;<endl>)");
check("ind", "\t\t");
check("var", v.first);
check("type", get<1>(v.second));
u256 memIdx = index * 0x20;
string val{};
if (memoryDump.count(memIdx))
{
unsigned byteWidth = get<2>(v.second);
val = extractBytes(memoryDump.at(memIdx), byteWidth);
// Avoid interpretation of 20 byte literals as address literals
// by prepending 00.
if (byteWidth == 20)
val = "00" + val;
}
else
val = "0";
check("value", val);
check("error", to_string(error++));
check("endl", "\n");
out << check.render();
index++;
}
out << "\t\treturn 0;\n";
return out.str();
}
map<u256, string> ProtoConverter::interpretYul(
string const& _yulSource,
langutil::EVMVersion _version,
ostringstream& _os
)
{
using namespace yul;
using namespace solidity::yul::test::yul_fuzzer;
YulStringRepository::reset();
// AssemblyStack entry point
AssemblyStack stack(
_version,
AssemblyStack::Language::StrictAssembly,
solidity::frontend::OptimiserSettings::full()
);
// Parse protobuf mutated YUL code
if (!stack.parseAndAnalyze("source", _yulSource) || !stack.parserResult()->code ||
!stack.parserResult()->analysisInfo)
{
std::cout << _yulSource << std::endl;
yulAssert(false, "Proto fuzzer generated malformed program");
}
stack.optimize();
yulFuzzerUtil i;
auto r = i.interpret(
_os,
stack.parserResult()->code,
EVMDialect::strictAssemblyForEVMObjects(_version)
);
if (r != yulFuzzerUtil::TerminationReason::StepLimitReached && r != yulFuzzerUtil::TerminationReason::TraceLimitReached)
return i.memoryDump();
else
throw langutil::FuzzerError();
}
string ProtoConverter::visit(Assembly const& _as)
{
using namespace solidity::yul::test;
ostringstream assemblyStr;
assemblyStr << "\t\t" << "assembly {\n";
// Yul converter
auto c = yul_fuzzer::ProtoConverter{m_varCounter};
m_yulAssembly = c.programToString(_as.p());
m_yulProgram = Whiskers("{<endl><init><assembly>}")
("endl", "\n")
("init", m_yulInitCode.str())
("assembly", m_yulAssembly)
.render();
assemblyStr << m_yulAssembly;
assemblyStr << "\t\t}\n";
return assemblyStr.str();
}
string ProtoConverter::visit(VarDecl const& _vardecl)
{
Whiskers v(R"(<type> <varName> = <type>(<value>);)");
string type = visit(_vardecl.t());
string varName = newVarName();
unsigned byteWidth = widthUnsigned(_vardecl.t().bytewidth());
m_varTypeMap.emplace(varName, tuple(typeSign(_vardecl.t()), type, byteWidth));
v("type", type);
v("varName", varName);
string value = maskUnsignedToHex(64);
v("value", value);
Whiskers i(R"(let <varName> := <value><endl>)");
i("varName", varName);
i("value", "0x" + extractBytes(value, byteWidth));
i("endl", "\n");
m_yulInitCode << i.render();
incrementVarCounter();
return "\t\t" + v.render() + '\n';
}
string ProtoConverter::visit(Type const& _type)
{
return signString(_type.s()) + widthString(_type.bytewidth());
}

View File

@ -0,0 +1,121 @@
#include <test/tools/ossfuzz/solArith.pb.h>
#include "yulFuzzerCommon.h"
#include <liblangutil/EVMVersion.h>
#include <libsolutil/Common.h>
#include <libsolutil/FixedHash.h>
#include <libsolutil/Keccak256.h>
#include <random>
namespace solidity::test::solarithfuzzer
{
/// Random number generator that is seeded with a fuzzer
/// supplied unsigned integer.
struct SolRandomNumGenerator
{
using RandomEngine = std::mt19937_64;
explicit SolRandomNumGenerator(unsigned _seed): m_random(RandomEngine(_seed)) {}
/// @returns a pseudo random unsigned integer
unsigned operator()()
{
return m_random();
}
RandomEngine m_random;
};
enum class Sign
{
Signed,
Unsigned
};
class ProtoConverter
{
public:
ProtoConverter()
{
}
ProtoConverter(ProtoConverter const&) = delete;
ProtoConverter(ProtoConverter&&) = delete;
std::string programToString(Program const& _input);
private:
std::string visit(Type const& _type);
std::string visit(Assembly const& _a);
std::string visit(VarDecl const& _decl);
std::string visit(Block const& _block);
std::string visit(Program const& _program);
std::map<u256, std::string> interpretYul(
std::string const& _yulSource,
langutil::EVMVersion _version,
std::ostringstream& _os
);
std::string addChecks(
std::string const& _yulSource,
langutil::EVMVersion _version,
std::ostringstream& _os
);
std::string newVarName()
{
return "v" + std::to_string(m_varCounter);
}
void incrementVarCounter()
{
m_varCounter++;
}
static std::string signString(Type::Sign _sign)
{
return _sign == Type::Sign::Type_Sign_SIGNED ? "int" : "uint";
}
static std::string signString(Sign _sign)
{
return _sign == Sign::Signed ? "int" : "uint";
}
static std::string widthString(unsigned _width)
{
return std::to_string((_width % 32 + 1) * 8);
}
static unsigned widthUnsigned(unsigned _width)
{
return _width % 32 + 1;
}
bool varAvailable()
{
return m_varCounter > 0;
}
Sign typeSign(Type const& _ts)
{
return _ts.s() == Type::SIGNED ? Sign::Signed : Sign::Unsigned;
}
std::string maskUnsignedToHex(unsigned _numMaskNibbles)
{
return toHex(maskUnsignedInt(_numMaskNibbles), util::HexPrefix::Add);
}
// Convert _counter to string and return its keccak256 hash
solidity::u256 hashUnsignedInt()
{
return util::keccak256(util::h256((*m_rand)()));
}
u256 maskUnsignedInt(unsigned _numMaskNibbles)
{
return hashUnsignedInt() & u256("0x" + std::string(_numMaskNibbles, 'f'));
}
static std::string extractBytes(std::string _value, unsigned _numBytes)
{
return _value.substr(_value.size() - (_numBytes * 2));
}
unsigned m_varCounter = 0;
std::shared_ptr<SolRandomNumGenerator> m_rand;
std::map<std::string, std::tuple<Sign, std::string, unsigned>> m_varTypeMap;
std::string m_yulAssembly;
std::string m_yulProgram;
std::ostringstream m_yulInitCode;
};
}

View File

@ -29,12 +29,11 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret(
size_t _maxTraceSize
)
{
InterpreterState state;
state.maxTraceSize = _maxTraceSize;
state.maxSteps = _maxSteps;
m_state.maxTraceSize = _maxTraceSize;
m_state.maxSteps = _maxSteps;
// Add 64 bytes of pseudo-randomly generated calldata so that
// calldata opcodes perform non trivial work.
state.calldata = {
m_state.calldata = {
0xe9, 0x96, 0x40, 0x7d, 0xa5, 0xda, 0xb0, 0x2d,
0x97, 0xf5, 0xc3, 0x44, 0xd7, 0x65, 0x0a, 0xd8,
0x2c, 0x14, 0x3a, 0xf3, 0xe7, 0x40, 0x0f, 0x1e,
@ -44,7 +43,24 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret(
0xc7, 0x60, 0x5f, 0x7c, 0xcd, 0xfb, 0x92, 0xcd,
0x8e, 0xf3, 0x9b, 0xe4, 0x4f, 0x6c, 0x14, 0xde
};
Interpreter interpreter(state, _dialect);
m_state.returndata = {
0xe9, 0x96, 0x40, 0x7d, 0xa5, 0xda, 0xb0, 0x2d,
0x97, 0xf5, 0xc3, 0x44, 0xd7, 0x65, 0x0a, 0xd8,
0x2c, 0x14, 0x3a, 0xf3, 0xe7, 0x40, 0x0f, 0x1e,
0x67, 0xce, 0x90, 0x44, 0x2e, 0x92, 0xdb, 0x88,
0xb8, 0x43, 0x9c, 0x41, 0x42, 0x08, 0xf1, 0xd7,
0x65, 0xe9, 0x7f, 0xeb, 0x7b, 0xb9, 0x56, 0x9f,
0xc7, 0x60, 0x5f, 0x7c, 0xcd, 0xfb, 0x92, 0xcd,
0x8e, 0xf3, 0x9b, 0xe4, 0x4f, 0x6c, 0x14, 0xde
};
// for (unsigned i = 0; i < 64; i++)
// {
// m_state.memory[i] = m_state.calldata[i];
// if (i % 32 == 0)
// m_state.storage[util::h256(i / 32)] = util::h256(m_state.calldata[i]);
// }
Interpreter interpreter(m_state, _dialect);
TerminationReason reason = TerminationReason::None;
try
@ -64,6 +80,11 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret(
reason = TerminationReason::ExplicitlyTerminated;
}
state.dumpTraceAndState(_os);
m_state.dumpTraceAndState(_os);
return reason;
}
std::map<u256, std::string> yulFuzzerUtil::memoryDump()
{
return m_state.dumpMemory();
}

View File

@ -30,13 +30,15 @@ struct yulFuzzerUtil
None
};
static TerminationReason interpret(
TerminationReason interpret(
std::ostream& _os,
std::shared_ptr<yul::Block> _ast,
Dialect const& _dialect,
size_t _maxSteps = maxSteps,
size_t _maxTraceSize = maxTraceSize
);
std::map<u256, std::string> memoryDump();
InterpreterState m_state;
static size_t constexpr maxSteps = 100;
static size_t constexpr maxTraceSize = 75;
};

View File

@ -17,82 +17,17 @@
syntax = "proto2";
message VarDecl {
required Expression expr = 1;
}
message MultiVarDecl {
required uint32 num_vars = 1;
}
message LowLevelCall {
enum Type {
CALL = 0;
CALLCODE = 1;
DELEGATECALL = 2;
STATICCALL = 3;
}
required Type callty = 1;
required Expression gas = 2;
required Expression addr = 3;
// Valid for call and callcode only
required Expression wei = 4;
required Expression in = 5;
required Expression insize = 6;
required Expression out = 7;
required Expression outsize = 8;
}
message Create {
enum Type {
CREATE = 0;
CREATE2 = 1;
}
required Type createty = 1;
required Expression wei = 2;
required Expression position = 3;
required Expression size = 4;
// Valid for create2 only
required Expression value = 5;
}
message FunctionCall {
enum Returns {
ZERO = 1;
SINGLE = 2;
MULTIDECL = 3;
MULTIASSIGN = 4;
}
required Returns ret = 1;
// Indexes an existing function
required uint32 func_index = 2;
required Expression in_param1 = 3;
required Expression in_param2 = 4;
required Expression in_param3 = 5;
required Expression in_param4 = 6;
required VarRef out_param1 = 7;
required VarRef out_param2 = 8;
required VarRef out_param3 = 9;
required VarRef out_param4 = 10;
}
message TypedVarDecl {
enum TypeName {
BOOL = 1;
U8 = 2;
U32 = 3;
U64 = 4;
U128 = 5;
U256 = 6;
S8 = 7;
S32 = 8;
S64 = 9;
S128 = 10;
S256 = 11;
};
required int32 id = 1;
required TypeName type = 2;
required Expression expr = 3;
required uint32 func_index = 1;
required Expression in_param1 = 2;
required Expression in_param2 = 3;
required Expression in_param3 = 4;
required Expression in_param4 = 5;
required VarRef out_param1 = 6;
required VarRef out_param2 = 7;
required VarRef out_param3 = 8;
required VarRef out_param4 = 9;
}
message VarRef {
@ -108,24 +43,6 @@ message Literal {
}
}
message TypedLiteral {
enum TypeName {
BOOL = 1;
U8 = 2;
U32 = 3;
U64 = 4;
U128 = 5;
U256 = 6;
S8 = 7;
S32 = 8;
S64 = 9;
S128 = 10;
S256 = 11;
};
required int32 val = 1;
required TypeName type = 2;
}
message BinaryOp {
enum BOp {
ADD = 0;
@ -163,24 +80,11 @@ message UnaryOp {
SLOAD = 2;
ISZERO = 3;
CALLDATALOAD = 4;
EXTCODESIZE = 5;
EXTCODEHASH = 6;
BALANCE = 7;
BLOCKHASH = 8;
}
required UOp op = 1;
required Expression operand = 2;
}
message UnaryOpData {
enum UOpData {
SIZE = 1;
OFFSET = 2;
}
required UOpData op = 1;
required uint64 identifier = 2;
}
message TernaryOp {
enum TOp {
ADDM = 0;
@ -192,33 +96,9 @@ message TernaryOp {
required Expression arg3 = 4;
}
message CopyFunc {
enum CopyType {
CALLDATA = 0;
CODE = 1;
RETURNDATA = 2;
DATA = 3;
}
required CopyType ct = 1;
required Expression target = 2;
required Expression source = 3;
required Expression size = 4;
}
message ExtCodeCopy {
required Expression addr = 1;
required Expression target = 2;
required Expression source = 3;
required Expression size = 4;
}
message NullaryOp {
enum NOp {
PC = 1;
MSIZE = 2;
GAS = 3;
CALLDATASIZE = 4;
CODESIZE = 5;
RETURNDATASIZE = 6;
ADDRESS = 7;
ORIGIN = 8;
@ -226,7 +106,6 @@ message NullaryOp {
CALLVALUE = 10;
GASPRICE = 11;
COINBASE = 12;
TIMESTAMP = 13;
NUMBER = 14;
DIFFICULTY = 15;
GASLIMIT = 16;
@ -247,21 +126,12 @@ message StoreFunc {
required Storage st = 3;
}
message LogFunc {
enum NumTopics {
ZERO = 0;
ONE = 1;
TWO = 2;
THREE = 3;
FOUR = 4;
}
required Expression pos = 1;
required Expression size = 2;
required NumTopics num_topics = 3;
required Expression t1 = 4;
required Expression t2 = 5;
required Expression t3 = 6;
required Expression t4 = 7;
message FunctionExpr {
required uint64 index = 1;
required Expression in_param1 = 2;
required Expression in_param2 = 3;
required Expression in_param3 = 4;
required Expression in_param4 = 5;
}
message Expression {
@ -272,10 +142,7 @@ message Expression {
UnaryOp unop = 4;
TernaryOp top = 5;
NullaryOp nop = 6;
FunctionCall func_expr = 7;
LowLevelCall lowcall = 8;
Create create = 9;
UnaryOpData unopdata = 10;
FunctionExpr funcexpr = 10;
}
}
@ -286,15 +153,15 @@ message AssignmentStatement {
message IfStmt {
required Expression cond = 1;
required Block if_body = 2;
required Block block = 2;
}
message BoundedForStmt {
required Block for_body = 1;
required Block block = 1;
}
message ForStmt {
required Block for_body = 1;
required Block block = 1;
required Block for_init = 2;
required Block for_post = 3;
required Expression for_cond = 4;
@ -302,48 +169,18 @@ message ForStmt {
message CaseStmt {
required Literal case_lit = 1;
required Block case_block = 2;
required Block block = 2;
}
message SwitchStmt {
required Expression switch_expr = 1;
repeated CaseStmt case_stmt = 2;
optional Block default_block = 3;
optional Block block = 3;
}
message BreakStmt {}
message ContinueStmt {}
message StopInvalidStmt {
enum Type {
STOP = 0;
INVALID = 1;
}
required Type stmt = 1;
}
message RetRevStmt {
enum Type {
RETURN = 0;
REVERT = 1;
}
required Type stmt = 1;
required Expression pos = 2;
required Expression size = 3;
}
message SelfDestructStmt {
required Expression addr = 1;
}
message TerminatingStmt {
oneof term_oneof {
StopInvalidStmt stop_invalid = 1;
RetRevStmt ret_rev = 2;
SelfDestructStmt self_des = 3;
}
}
message FunctionDef {
required uint32 num_input_params = 1;
required uint32 num_output_params = 2;
@ -359,7 +196,6 @@ message LeaveStmt {}
message Statement {
oneof stmt_oneof {
VarDecl decl = 1;
AssignmentStatement assignment = 2;
IfStmt ifstmt = 3;
StoreFunc storage_func = 4;
@ -368,16 +204,11 @@ message Statement {
SwitchStmt switchstmt = 7;
BreakStmt breakstmt = 8;
ContinueStmt contstmt = 9;
LogFunc log_func = 10;
CopyFunc copy_func = 11;
ExtCodeCopy extcode_copy = 12;
TerminatingStmt terminatestmt = 13;
FunctionCall functioncall = 14;
BoundedForStmt boundedforstmt = 15;
FunctionDef funcdef = 16;
PopStmt pop = 17;
LeaveStmt leave = 18;
MultiVarDecl multidecl = 19;
}
}
@ -385,36 +216,8 @@ message Block {
repeated Statement statements = 1;
}
message Object {
required Code code = 1;
optional Data data = 2;
optional Object sub_obj = 3;
}
message Code {
message Program {
required Block block = 1;
}
message Data {
required string hex = 1;
}
message Program {
enum Version {
HOMESTEAD = 0;
TANGERINE = 1;
SPURIOUS = 2;
BYZANTIUM = 3;
CONSTANTINOPLE = 4;
PETERSBURG = 5;
ISTANBUL = 6;
BERLIN = 7;
}
oneof program_oneof {
Block block = 1;
Object obj = 2;
}
required Version ver = 3;
}
package solidity.yul.test.yul_fuzzer;
package solidity.yul.test.yul_fuzzer;

View File

@ -96,7 +96,7 @@ DEFINE_PROTO_FUZZER(Program const& _input)
return;
stack.optimize();
termReason = yulFuzzerUtil::interpret(
yulFuzzerUtil::interpret(
os2,
stack.parserResult()->code,
EVMDialect::strictAssemblyForEVMObjects(version),

View File

@ -46,6 +46,18 @@ using namespace solidity::yul::test;
using solidity::util::h256;
std::map<u256, std::string> InterpreterState::dumpMemory() const
{
std::map<u256, std::string> out;
map<u256, u256> words;
for (auto const& [offset, value]: memory)
words[(offset / 0x20) * 0x20] |= u256(uint32_t(value)) << (256 - 8 - 8 * size_t(offset % 0x20));
for (auto const& [offset, value]: words)
if (value != 0)
out.emplace(offset, h256(value).hex());
return out;
}
void InterpreterState::dumpTraceAndState(ostream& _out) const
{
_out << "Trace:" << endl;

View File

@ -94,6 +94,7 @@ struct InterpreterState
ControlFlowState controlFlowState = ControlFlowState::Default;
void dumpTraceAndState(std::ostream& _out) const;
std::map<u256, std::string> dumpMemory() const;
};
/**