diff --git a/liblangutil/Exceptions.h b/liblangutil/Exceptions.h index b8688afa6..908ff7904 100644 --- a/liblangutil/Exceptions.h +++ b/liblangutil/Exceptions.h @@ -42,6 +42,7 @@ struct InternalCompilerError: virtual util::Exception {}; struct FatalError: virtual util::Exception {}; struct UnimplementedFeatureError: virtual util::Exception {}; struct InvalidAstError: virtual util::Exception {}; +struct FuzzerError: virtual util::Exception {}; /// Assertion that throws an InternalCompilerError containing the given description if it is not met. #define solAssert(CONDITION, DESCRIPTION) \ diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 089d0f5cf..2ae54749a 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -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] ... - solAssert( + assertThrow( 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, + CompilerError, "Stack too deep, try removing local variables." ); // fetch target storage reference diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 156cee565..bf68696ce 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -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, + CompilerError, + "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, + CompilerError, "Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables." ); m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2; @@ -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, + CompilerError, + "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, + CompilerError, + "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, + CompilerError, + "Stack too deep, try removing local variables." + ); for (unsigned i = 1; i < _items; ++i) m_context << swapInstruction(i); } diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index eefff5b18..000bb96a3 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -218,8 +218,9 @@ void CodeGenerator::assemble( } catch (StackTooDeepError const& _e) { - yulAssert( + assertThrow( false, + CompilerError, "Stack too deep when compiling inline assembly" + (_e.comment() ? ": " + *_e.comment() : ".") ); diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp index e4de0f185..c1839d3f8 100644 --- a/test/tools/fuzzer_common.cpp +++ b/test/tools/fuzzer_common.cpp @@ -85,7 +85,7 @@ void FuzzerUtil::testCompiler(string const& _input, bool _optimize) compiler.setOptimiserSettings(optimiserSettings); try { - compiler.compile(); + solAssert(compiler.compile(), "Compilation failed"); } catch (Error const&) { @@ -96,6 +96,12 @@ void FuzzerUtil::testCompiler(string const& _input, bool _optimize) catch (UnimplementedFeatureError const&) { } + catch (CompilerError const&) + { + } + catch (StackTooDeepException const&) + { + } } void FuzzerUtil::runCompiler(string const& _input, bool _quiet) diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 83543b9f0..6855bce4e 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -10,7 +10,7 @@ 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 sol_proto_ossfuzz) add_custom_target(ossfuzz_abiv2) add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz) @@ -60,6 +60,25 @@ 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 +# /home/bhargava/work/github/solidity/deps/libprotobuf-mutator +# /home/bhargava/work/github/solidity/deps/LPM/external.protobuf/include +# ) +# 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 +97,26 @@ if (OSSFUZZ) protobuf.a ) set_target_properties(abiv2_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 + ) + 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 diff --git a/test/tools/ossfuzz/SolProtoAdaptor.cpp b/test/tools/ossfuzz/SolProtoAdaptor.cpp new file mode 100644 index 000000000..ca8c97d80 --- /dev/null +++ b/test/tools/ossfuzz/SolProtoAdaptor.cpp @@ -0,0 +1,1530 @@ +/* + 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 + +using namespace solidity::test::solprotofuzzer::adaptor; +using namespace solidity::test::solprotofuzzer; +using namespace solidity::util; +using namespace std; + +namespace +{ +SolFunctionStateMutability mutabilityConverter(InterfaceFunction_StateMutability _mut) +{ + switch (_mut) + { + case InterfaceFunction_StateMutability_PURE: + return SolFunctionStateMutability::PURE; + case InterfaceFunction_StateMutability_VIEW: + return SolFunctionStateMutability::VIEW; + case InterfaceFunction_StateMutability_PAYABLE: + return SolFunctionStateMutability::PAYABLE; + case InterfaceFunction_StateMutability_NONPAYABLE: + return SolFunctionStateMutability::NONPAYABLE; + } +} + +SolFunctionStateMutability mutabilityConverter(ContractFunction_StateMutability _mut) +{ + switch (_mut) + { + case ContractFunction_StateMutability_PURE: + return SolFunctionStateMutability::PURE; + case ContractFunction_StateMutability_VIEW: + return SolFunctionStateMutability::VIEW; + case ContractFunction_StateMutability_PAYABLE: + return SolFunctionStateMutability::PAYABLE; + case ContractFunction_StateMutability_NONPAYABLE: + return SolFunctionStateMutability::NONPAYABLE; + } +} + +SolLibraryFunctionStateMutability mutabilityConverter(LibraryFunction_StateMutability _mut) +{ + switch (_mut) + { + case LibraryFunction_StateMutability_PURE: + return SolLibraryFunctionStateMutability::PURE; + case LibraryFunction_StateMutability_VIEW: + return SolLibraryFunctionStateMutability::VIEW; + case LibraryFunction_StateMutability_NONPAYABLE: + return SolLibraryFunctionStateMutability::NONPAYABLE; + } +} + +SolFunctionVisibility visibilityConverter(ContractFunction_Visibility _vis) +{ + switch (_vis) + { + case ContractFunction_Visibility_PUBLIC: + return SolFunctionVisibility::PUBLIC; + case ContractFunction_Visibility_PRIVATE: + return SolFunctionVisibility::PRIVATE; + case ContractFunction_Visibility_EXTERNAL: + return SolFunctionVisibility::EXTERNAL; + case ContractFunction_Visibility_INTERNAL: + return SolFunctionVisibility::INTERNAL; + } +} + +SolFunctionVisibility visibilityConverter(LibraryFunction_Visibility _vis) +{ + switch (_vis) + { + case LibraryFunction_Visibility_PUBLIC: + return SolFunctionVisibility::PUBLIC; + case LibraryFunction_Visibility_PRIVATE: + return SolFunctionVisibility::PRIVATE; + case LibraryFunction_Visibility_EXTERNAL: + return SolFunctionVisibility::EXTERNAL; + case LibraryFunction_Visibility_INTERNAL: + return SolFunctionVisibility::INTERNAL; + } +} + +string functionVisibility(SolFunctionVisibility _vis) +{ + switch (_vis) + { + case SolFunctionVisibility::PUBLIC: + return "public"; + case SolFunctionVisibility::PRIVATE: + return "private"; + case SolFunctionVisibility::EXTERNAL: + return "external"; + case SolFunctionVisibility::INTERNAL: + return "internal"; + } +} + +string functionMutability(SolFunctionStateMutability _mut) +{ + switch (_mut) + { + case SolFunctionStateMutability::PURE: + return "pure"; + case SolFunctionStateMutability::VIEW: + return "view"; + case SolFunctionStateMutability::PAYABLE: + return "payable"; + case SolFunctionStateMutability::NONPAYABLE: + return ""; + } +} + +string libraryFunctionMutability(SolLibraryFunctionStateMutability _mut) +{ + switch (_mut) + { + case SolLibraryFunctionStateMutability::PURE: + return "pure"; + case SolLibraryFunctionStateMutability::VIEW: + return "view"; + case SolLibraryFunctionStateMutability::NONPAYABLE: + return ""; + } +} +} + +void SolInterfaceFunction::markExplicitOverride(string _contractName) +{ + m_type = Type::EXPLICITOVERRIDE; + m_overriddenFrom.clear(); + m_contractName = _contractName; + m_overriddenFrom.push_back(m_contractName); +} + +SolInterfaceFunction::SolInterfaceFunction( + string _functionName, + SolFunctionStateMutability _mutability, + Type _type, + string _contractName +) +{ + m_functionName = _functionName; + m_mutability = _mutability; + m_type = _type; + m_overriddenFrom.push_back(_contractName); + m_contractName = _contractName; +} + +bool SolInterfaceFunction::namesake(SolContractFunction const& _rhs) const +{ + // TODO: Change this once we permit arbitrary function parameter types + return name() == _rhs.name(); +} + +bool SolInterfaceFunction::namesake(SolInterfaceFunction const& _rhs) const +{ + // TODO: Change this once we permit arbitrary function parameter types + return name() == _rhs.name(); +} + +bool SolInterfaceFunction::operator==(SolInterfaceFunction const& _rhs) const +{ + solAssert(namesake(_rhs), "Sol proto adaptor: Cannot compare two interface functions with different names"); + return m_mutability == _rhs.m_mutability; +} + +bool SolInterfaceFunction::operator!=(SolInterfaceFunction const& _rhs) const +{ + solAssert(namesake(_rhs), "Sol proto adaptor: Comparing two interface functions with different names is redundant"); + return m_mutability != _rhs.m_mutability; +} + +void SolInterfaceFunction::merge(SolInterfaceFunction const& _rhs) +{ + assertThrow( + this->operator==(_rhs), + langutil::FuzzerError, + "Sol proto adaptor: Invalid inheritance hierarchy" + ); + m_type = Type::EXPLICITOVERRIDE; + for (auto &b: _rhs.m_overriddenFrom) + m_overriddenFrom.push_back(b); +#if 0 + std::cout << "overridden base names " << overriddenFromBaseNames() << std::endl; +#endif +} + +bool SolInterfaceFunction::operator==(SolContractFunction const& _rhs) const +{ + // TODO: Change this once we permit arbitrary function parameter types + return name() == _rhs.name(); +} + +bool SolInterfaceFunction::operator!=(SolContractFunction const& _rhs) const +{ + // TODO: Change this once we permit arbitrary function parameter types + return name() != _rhs.name(); +} + +string SolInterfaceFunction::overriddenFromBaseNames() const +{ + ostringstream nameStr; + string separator{}; + for (auto &b: m_overriddenFrom) + { + nameStr << separator << b; + if (separator.empty()) + separator = ", "; + } + return nameStr.str(); +} + +string SolInterfaceFunction::str() const +{ + if (explicitOverride()) + return Whiskers(R"( + function () override() + external returns (uint);)") + ("functionName", name()) + ("isMultiple", multipleBases()) + ("baseNames", overriddenFromBaseNames()) + ("stateMutability", functionMutability(mutability())) + .render(); + else if (memberFunction()) + return Whiskers(R"( + function () external returns (uint);)") + ("functionName", name()) + ("stateMutability", functionMutability(mutability())) + .render(); + else + return ""; +} + +string SolContractFunction::overriddenFromBaseNames() const +{ + ostringstream nameStr; + string separator{}; + for (auto &b: m_overriddenFrom) + { + nameStr << separator << b; + if (separator.empty()) + separator = ", "; + } + return nameStr.str(); +} + +void SolContractFunction::merge(SolContractFunction const& _rhs) +{ + assertThrow( + this->operator==(_rhs), + langutil::FuzzerError, + "Sol proto adaptor: Invalid inheritance hierarchy" + ); + m_type = Type::EXPLICITOVERRIDECONTRACT; + for (auto &b: _rhs.m_overriddenFrom) + m_overriddenFrom.push_back(b); +} + +void SolContractFunction::merge(SolInterfaceFunction const& _rhs) +{ + assertThrow( + this->operator==(_rhs), + langutil::FuzzerError, + "Sol proto adaptor: Invalid inheritance hierarchy" + ); + m_type = Type::EXPLICITOVERRIDEINTERFACE; + for (auto &b: _rhs.m_overriddenFrom) + m_overriddenFrom.push_back(b); +} + +SolContractFunction::SolContractFunction( + vector _overriddenFrom, + SolFunctionStateMutability _mutability, + std::string _contractName, + std::string _functionName, + Type _type, + bool _implemented, + bool _virtual, + std::string _returnValue, + SolFunctionVisibility _vis +) +{ + for (auto &s: _overriddenFrom) + m_overriddenFrom.push_back(s); + m_contractName = _contractName; + m_functionName = _functionName; + m_type = _type; + m_implemented = _implemented; + m_returnValue = _returnValue; + m_visibility = _vis; + m_mutability = _mutability; + // Unimplemented contract functions must be marked virtual + if (explicitOverride() || memberFunction()) + { + if (_implemented) + m_virtual = _virtual; + else + m_virtual = true; + } +} + +SolContractFunction::SolContractFunction( + ContractFunction const& _function, + std::string _contractName, + std::string _functionName, + Type _type, + bool _implemented, + std::string _returnValue +) +{ + m_contractName = _contractName; + m_overriddenFrom.push_back(m_contractName); + m_functionName = _functionName; + m_visibility = visibilityConverter(_function.vis()); + m_mutability = mutabilityConverter(_function.mut()); + m_type = _type; + m_returnValue = _returnValue; + m_implemented = _implemented; + // Unimplemented contract functions must be marked virtual + if (explicitOverride() || memberFunction()) + { + if (_implemented) + m_virtual = _function.virtualfunc(); + else + m_virtual = true; + } + // TODO: Perhaps not hard fail here. Silently ignoring instead. + assertThrow( + !disallowed(), + langutil::FuzzerError, + "Sol proto adaptor: Invalid parameters for a contract function e.g., private+virtual" + ); +} + +bool SolContractFunction::namesake(SolContractFunction const& _rhs) const +{ + return name() == _rhs.name(); +} + +bool SolContractFunction::namesake(SolInterfaceFunction const& _rhs) const +{ + return name() == _rhs.name(); +} + +bool SolContractFunction::operator==(SolContractFunction const& _rhs) const +{ + solAssert(namesake(_rhs), "Sol proto adaptor: Cannot compare two contract functions with different names"); + return m_mutability == _rhs.m_mutability && m_visibility == _rhs.m_visibility; +} + +bool SolContractFunction::operator!=(SolContractFunction const& _rhs) const +{ + solAssert(namesake(_rhs), "Sol proto adaptor: Comparing two contract functions with different names is redundant"); + return m_mutability != _rhs.m_mutability || m_visibility != _rhs.m_visibility; +} + +bool SolContractFunction::operator==(SolInterfaceFunction const& _rhs) const +{ + solAssert(namesake(_rhs), "Sol proto adaptor: Cannot compare a contract function with a differently named interface function"); + return m_mutability == _rhs.m_mutability && m_visibility == SolFunctionVisibility::EXTERNAL; +} + +bool SolContractFunction::operator!=(SolInterfaceFunction const& _rhs) const +{ + solAssert(namesake(_rhs), "Sol proto adaptor: Cannot compare a contract function with a differently named interface function"); + return m_mutability != _rhs.m_mutability || m_visibility != SolFunctionVisibility::EXTERNAL; +} + +bool SolContractFunction::disallowed() const +{ + // Private virtual functions are disallowed + if (visibility() == SolFunctionVisibility::PRIVATE && isVirtual()) + return true; + // Private payable functions are disallowed + else if (visibility() == SolFunctionVisibility::PRIVATE && mutability() == SolFunctionStateMutability::PAYABLE) + return true; + // Internal payable functions are disallowed + else if (visibility() == SolFunctionVisibility::INTERNAL && mutability() == SolFunctionStateMutability::PAYABLE) + return true; + return false; +} + +string SolContractFunction::str() const +{ + if (implicitOverride()) + return ""; + + bool override = false; + bool multiOverride = false; + if (explicitOverride()) + { + override = true; + multiOverride = m_overriddenFrom.size() > 1; + } + string bodyStr = Whiskers(R"( + { + return ; + })") + ("uint", returnValue()) + .render(); + + return Whiskers(R"( + function () virtual + override() + returns (uint);)") + ("functionName", name()) + ("isVirtual", isVirtual()) + ("visibility", functionVisibility(visibility())) + ("stateMutability", functionMutability(mutability())) + ("isOverride", override) + ("isMultiple", multiOverride) + ("baseNames", overriddenFromBaseNames()) + ("isImplemented", implemented()) + ("body", bodyStr) + .render(); +} + +SolLibraryFunction::SolLibraryFunction( + LibraryFunction const& _function, + std::string _libraryName, + std::string _functionName, + std::string _returnValue +) +{ + m_libraryName = _libraryName; + m_functionName = _functionName; + m_visibility = visibilityConverter(_function.vis()); + m_mutability = mutabilityConverter(_function.mut()); + m_returnValue = _returnValue; +} + +string SolLibraryFunction::str() const +{ + string bodyStr = Whiskers(R"( + { + return ; + })") + ("uint", returnValue()) + .render(); + + return Whiskers(R"( + function (uint) returns (uint))") + ("functionName", name()) + ("visibility", functionVisibility(visibility())) + ("stateMutability", libraryFunctionMutability(mutability())) + ("body", bodyStr) + .render(); +} + +void SolInterface::merge() +{ + /* Merge algorithm: + * 1. Deep copy all base interface functions (local) into a single list (global) + * 2. Mark all of these as implicit overrides + * 3. Iterate list of implicit overrides + * 3a. If n-way merge is necessary, do so and mark a two-base explicit override and add to contract + * 3b. If n-way merge is not possible, add as implicit override to contract + * 4. Update ownership of n-way merges + * 5. Iterate list of contract implicit and explicit (2-way) overrides + * 5a. If implicit, pseudo randomly mark it explicit + */ +#if 0 + std::cout << "Function index before merge " << m_functionIndex << std::endl; +#endif + + // Step 1-2 + vector> baseFunctions{}; + for (auto &base: m_baseInterfaces) + { + // At the end of this loop, this contract will resume function + // numbering from the base with the highest number of member + // functions. + if (m_functionIndex < base->functionIndex()) + m_functionIndex = base->functionIndex(); + for (auto &bf: base->m_functions) + baseFunctions.push_back(make_shared(*bf)); + for (auto &l: baseFunctions) + { + // Reset override history for past n-way merge + if (l->explicitOverride() && l->numOverriddenFromBases() > 1) + l->resetOverriddenBases(); + // Mark all as implicit overrides + l->markImplicitOverride(); + } + } + // Step 3 + vector> updateList; + set> processedFunctionBasePairs; + for (auto &f: baseFunctions) + { + if (processedFunctionBasePairs.count(pair(f->name(), f->m_contractName))) + continue; + else + processedFunctionBasePairs.insert(pair(f->name(), f->m_contractName)); +#if 0 + std::cout << "Processing " << f->name() << " from " << f->m_contractName << std::endl; +#endif + bool merged = false; + for (auto &e: m_functions) + { +#if 0 + std::cout << "Comparing " << e->name() << " from " << e->m_contractName << " with " << f->name() << std::endl; +#endif + if (e->namesake(*f)) + { +#if 0 + std::cout << "n-way merge of " << f->name() << " from " << f->m_contractName << std::endl; +#endif + e->merge(*f); + updateList.push_back(e); + merged = true; + break; + } + } + if (!merged) + { +#if 0 + std::cout << "Adding " << f->name() << " to " << name() << std::endl; +#endif + m_functions.push_back(f); + } + + } + // Step 4 + for (auto &u: updateList) + u->m_contractName = name(); + // Step 5 + for (auto &e: m_functions) + if (e->implicitOverride() && coinToss()) + e->markExplicitOverride(name()); + +#if 0 + std::cout << "Function index after merge " << m_functionIndex << std::endl; +#endif +} + +void SolInterface::addBases(Interface const& _interface) +{ + vector> cyclicBaseList; + for (auto &b: _interface.bases()) + { + shared_ptr cyclicBase; + if (!cyclicBaseList.empty()) + cyclicBase = cyclicBaseList[random() % cyclicBaseList.size()]; + auto base = make_shared(SolInterface(b, newBaseName(), cyclicBase, m_prng)); +#if 0 + if (cyclicBase) + std::cout << "Added " << cyclicBase->name() << " as a cyclic base of " << base->name() << std::endl; +#endif + m_baseInterfaces.push_back(base); + cyclicBaseList.push_back(base); + if (base->atleastOneBase()) + cyclicBaseList.push_back(base->randomBase()); + +#if 0 + std::cout << "Base function index is " << base->functionIndex() << std::endl; +#endif + m_lastBaseName = base->lastBaseName(); +#if 0 + std::cout << "Function index before merge is called is " << m_functionIndex << std::endl; +#endif + } + // Add a cyclic base to this if it does not exist +// if (!m_cyclicBaseInterface && !cyclicBaseList.empty()) +// { +// auto cyclicBase = cyclicBaseList[random() % cyclicBaseList.size()]; +// if (find(m_baseInterfaces.begin(), m_baseInterfaces.end(), cyclicBase) == m_baseInterfaces.end()) +// { +// std::cout << "Adding " << cyclicBase->name() << " as a cyclic base of " << name() << std::endl; +// m_cyclicBaseInterface = cyclicBase; +// // Add to beginning since this could be more basic than other bases. +// m_baseInterfaces.insert(m_baseInterfaces.begin(), m_cyclicBaseInterface); +// } +// } +#if 0 + std::cout << m_functionIndex << std::endl; +#endif + merge(); +} + +void SolInterface::addFunctions(Interface const& _interface) +{ +#if 0 + std::cout << "Function index before addfunc is " << m_functionIndex << std::endl; +#endif + for (auto &f: _interface.funcdef()) + { + m_functions.push_back( + make_shared( + SolInterfaceFunction( + newFunctionName(), + mutabilityConverter(f.mut()), + SolInterfaceFunction::Type::MEMBERFUNCTION, + name() + ) + ) + ); + } +#if 0 + std::cout << "Function index after addfunc is " << m_functionIndex << std::endl; +#endif +} + +SolInterface::SolInterface( + Interface const& _interface, + string _name, + shared_ptr _cyclicBase, + shared_ptr _prng +) +{ + m_prng = _prng; + m_interfaceName = _name; + m_lastBaseName = m_interfaceName; + if (_cyclicBase) + { + m_cyclicBaseInterface = _cyclicBase; + m_baseInterfaces.push_back(_cyclicBase); + } + addBases(_interface); + addFunctions(_interface); +} + +string SolInterface::baseNames() const +{ + ostringstream bases; + string separator{}; + for (auto &b: m_baseInterfaces) + { + bases << separator << b->name(); + if (separator.empty()) + separator = ", "; + } + return bases.str(); +} + +string SolInterface::baseInterfaceStr() const +{ + ostringstream baseInterfaces; + for (auto &b: m_baseInterfaces) + if (!m_cyclicBaseInterface || b != m_cyclicBaseInterface) + baseInterfaces << b->str(); + + return baseInterfaces.str(); +} + +string SolInterface::functionStr() const +{ + ostringstream functions; + + for (auto &f: m_functions) + functions << f->str(); + return functions.str(); +} + +string SolInterface::str() const +{ + return Whiskers(R"( + +interface is { + +})") + ("bases", baseInterfaceStr()) + ("programName", name()) + ("inheritance", bases()) + ("baseNames", baseNames()) + ("functionDefs", functionStr()) + .render(); +} + +unsigned SolBaseContract::functionIndex() +{ + if (type() == BaseType::INTERFACE) + return interface()->functionIndex(); + else + { + solAssert(type() == BaseType::CONTRACT, "Sol proto adaptor: Invalid base contract"); + return contract()->functionIndex(); + } +} + +string SolBaseContract::lastBaseName() +{ + if (type() == BaseType::INTERFACE) + return interface()->lastBaseName(); + else + { + solAssert(type() == BaseType::CONTRACT, "Sol proto adaptor: Invalid base contract"); + return contract()->lastBaseName(); + } +} + +SolBaseContract::BaseType SolBaseContract::type() const +{ + if (holds_alternative>(m_base)) + return BaseType::INTERFACE; + else + { + solAssert(holds_alternative>(m_base), "Sol proto fuzzer: Invalid base contract"); + return BaseType::CONTRACT; + } +} + +string SolBaseContract::str() +{ + switch (type()) + { + case BaseType::INTERFACE: + return interface()->str(); + case BaseType::CONTRACT: + return contract()->str(); + } +} + +string SolBaseContract::name() +{ + if (type() == BaseType::INTERFACE) + return interface()->name(); + else + { + solAssert(type() == BaseType::CONTRACT, "Sol proto adaptor: Invalid base contract"); + return contract()->name(); + } +} + +std::shared_ptr SolBaseContract::randomBase() +{ + if (type() == BaseType::INTERFACE && interface()->atleastOneBase()) + { + return make_shared(SolBaseContract(interface()->randomBase())); + } + else if (type() == BaseType::CONTRACT && contract()->atleastOneBase()) + { + return contract()->randomBase(); + } + solAssert(false, "Sol proto adaptor: Invalid base contract"); +} + +bool SolBaseContract::atleastOneBase() +{ + if (type() == BaseType::INTERFACE) + return interface()->atleastOneBase(); + else + return contract()->atleastOneBase(); +} + +SolBaseContract::SolBaseContract(shared_ptr _base) +{ + m_base = _base; +} + +SolBaseContract::SolBaseContract( + ProtoBaseContract _base, + string _name, + shared_ptr _cyclicBase, + shared_ptr _prng +) +{ + if (holds_alternative(_base)) + m_base = make_shared( + SolContract(*get(_base), _name, _cyclicBase, _prng) + ); + else + { + solAssert(!_cyclicBase || _cyclicBase->type() == BaseType::INTERFACE, "Sol proto adaptor: Invalid interface base"); + solAssert(holds_alternative(_base), "Sol proto adaptor: Invalid base contract"); + shared_ptr baseInterface; + if (_cyclicBase) + baseInterface = _cyclicBase->interface(); + m_base = make_shared( + SolInterface(*get(_base), _name, baseInterface, _prng) + ); + } +} + +SolBaseContract::SolBaseContract( + ProtoBaseContract _base, + string _name, + shared_ptr _prng +) +{ + if (holds_alternative(_base)) + m_base = make_shared( + SolContract(*get(_base), _name, nullptr, _prng) + ); + else + { + solAssert(holds_alternative(_base), "Sol proto adaptor: Invalid base contract"); + m_base = make_shared( + SolInterface(*get(_base), _name, nullptr, _prng) + ); + } +} + +string SolContract::baseNames() const +{ + ostringstream bases; + string separator{}; + for (auto &b: m_baseContracts) + { + bases << separator << b->name(); + if (separator.empty()) + separator = ", "; + } + return bases.str(); +} + +bool SolContract::validTest() +{ + // If we have at least one function-expectation pair in this contract + // we have a valid test + if (!m_contractFunctionMap.empty()) + return true; + else + { + // If this contract does not offer a valid test, we check if its + // base contracts offer one. + for (auto &b: m_baseContracts) + { + if (b->type() == SolBaseContract::BaseType::CONTRACT) + { + if (b->contract()->validTest()) + return true; + } + } + return false; + } +} + +/// Must be called only when validTest() returns true. +void SolContract::validContractTests(map>& _testSet) +{ + string chosenContractName; + string chosenFunctionName{}; + string expectedOutput{}; + // Add test cases in current contract + if (!m_contractFunctionMap.empty()) + { + chosenContractName = name(); + _testSet[chosenContractName] = map{}; + for (auto &e: m_contractFunctionMap) + _testSet[chosenContractName].insert(make_pair(e.first, e.second)); + } + // Continue search in base contracts + for (auto &b: m_baseContracts) + if (b->type() == SolBaseContract::BaseType::CONTRACT) + if (b->contract()->validTest()) + b->contract()->validContractTests(_testSet); +} + +void SolContract::addFunctions(Contract const& _contract) +{ + bool abs = abstract(); + string contractName = name(); + // Add functions + for (auto &f: _contract.funcdef()) + { + auto function = make_shared( + SolContractFunction( + f, + contractName, + newFunctionName(), + SolContractFunction::Type::MEMBERFUNCTION, + (abs ? coinToss() : true), + newReturnValue() + ) + ); + m_functions.push_back(function); + // If contract is not abstract, add its public and external + // functions to contract function map. + if (!abs) + { + auto visibility = function->visibility(); + string functionName = function->name(); + string expectedOutput = function->returnValue(); + // Register only public and external contract functions because only they can + // be called from a different contract. + if (visibility == SolFunctionVisibility::PUBLIC || visibility == SolFunctionVisibility::EXTERNAL) + { + solAssert(!m_contractFunctionMap.count(functionName), "Sol proto adaptor: Duplicate contract function"); + m_contractFunctionMap.insert(pair(functionName, expectedOutput)); + } + } + } +} + +bool SolContract::mergeFunctionBasetoContract(FunctionType _f1, FunctionType _f2) +{ + /* There are four possible cases + * 1. _f1 (base function) and _f2 (contract) are interface functions + * 2. _f1 is interface function, _f2 is contract function + * 3. _f1 is contract function, _f2 is interface function + * 4. _f1 and _f2 are contract functions + */ + + // Merge status that is true only if there is a case for + // merging + bool merged = false; + + // Case 1 + if (_f1.index() == 1 && _f2.index() == 1) + { + auto function1 = get>(_f1); + auto function2 = get>(_f2); + if (function1->namesake(*function2)) + { + function1->merge(*function2); + // Create new contract function + auto mergedContract = make_shared( + SolContractFunction( + function1->m_overriddenFrom, + function1->mutability(), + name(), + function1->name(), + SolContractFunction::Type::EXPLICITOVERRIDEINTERFACE, + abstract() ? coinToss() : true, + coinToss(), + newReturnValue() + ) + ); + // Erase merged interface function + auto pos = find(m_functions.begin(), m_functions.end(), _f2); + m_functions.erase(pos); + m_functions.insert(pos, mergedContract); + merged = true; + } + } + // Case 2 + else if (_f1.index() == 1 && _f2.index() == 0) + { + auto function1 = get>(_f1); + auto function2 = get>(_f2); + if (function2->namesake(*function1)) + { + assertThrow( + function2->isVirtual(), + langutil::FuzzerError, + "Sol proto fuzzer: n-way merge of non-virtual contract function is not possible" + ); + function2->merge(*function1); + // If abstract contract, we may implement + bool implement = abstract() ? coinToss() : true; + // If merged function has already been implemented + // we must implement. + if (function2->implemented() && !implement) + implement = true; + // Create new contract function + auto mergedContract = make_shared( + SolContractFunction( + function2->m_overriddenFrom, + function2->mutability(), + name(), + function2->name(), + SolContractFunction::Type::EXPLICITOVERRIDECONTRACT, + implement, + coinToss(), + newReturnValue(), + function2->visibility() + ) + ); + // Erase merged interface function + auto pos = find(m_functions.begin(), m_functions.end(), _f2); + m_functions.erase(pos); + m_functions.insert(pos, mergedContract); + merged = true; + } + } + // Case 3 + else if (_f1.index() == 0 && _f2.index() == 1) + { + auto function1 = get>(_f1); + auto function2 = get>(_f2); + if (function1->namesake(*function2)) + { + // Assert contract function is virtual + assertThrow( + function1->isVirtual(), + langutil::FuzzerError, + "Sol proto fuzzer: n-way merge of non-virtual contract function is not possible" + ); + + function1->merge(*function2); + // If abstract contract, we may implement + bool implement = abstract() ? coinToss() : true; + // If merged function has already been implemented + // we must implement. + if (function1->implemented() && !implement) + implement = true; + // Create new contract function + auto mergedContract = make_shared( + SolContractFunction( + function1->m_overriddenFrom, + function1->mutability(), + name(), + function1->name(), + SolContractFunction::Type::EXPLICITOVERRIDEINTERFACE, + implement, + coinToss(), + newReturnValue() + ) + ); + // Erase merged interface function + auto pos = find(m_functions.begin(), m_functions.end(), _f2); + m_functions.erase(pos); + m_functions.insert(pos, mergedContract); + merged = true; + } + } + // Case 4 + else + { + auto function1 = get>(_f1); + auto function2 = get>(_f2); + if (function2->namesake(*function1)) + { + // Assert contract functions are virtual + assertThrow( + function1->isVirtual() && function2->isVirtual(), + langutil::FuzzerError, + "Sol proto fuzzer: n-way merge of non-virtual contract function is not possible" + ); + // Check if at least one base implements function + bool atleastOneImplements = function1->implemented() || function2->implemented(); + function1->merge(*function2); + // If abstract contract, we may implement + bool implement = abstract() ? coinToss() : true; + // If merged function has already been implemented by at + // least one of the bases, we must implement. + if (atleastOneImplements && !implement) + implement = true; + // Create new contract function + auto mergedContract = make_shared( + SolContractFunction( + function1->m_overriddenFrom, + function1->mutability(), + name(), + function1->name(), + SolContractFunction::Type::EXPLICITOVERRIDECONTRACT, + implement, + coinToss(), + newReturnValue(), + function1->visibility() + ) + ); + // Erase merged interface function + auto pos = find(m_functions.begin(), m_functions.end(), _f2); + m_functions.erase(pos); + m_functions.insert(pos, mergedContract); + merged = true; + } + } + return merged; +} + +void SolContract::copyFunctions(shared_ptr _base, FunctionList& _globalList) +{ + auto baseType = _base->type(); + solAssert( + baseType == SolBaseContract::BaseType::INTERFACE || baseType == SolBaseContract::BaseType::CONTRACT, + "Sol proto adaptor: Invalid base contract" + ); + + // Copy all base functions into global list + if (baseType == SolBaseContract::BaseType::INTERFACE) + for (auto& f: _base->interface()->m_functions) + _globalList.push_back(make_shared(*f)); + else + // Contract functions may be implicitly inherited + // interface function or contract function. + for (auto& f: _base->contract()->m_functions) + if (holds_alternative>(f)) + _globalList.push_back( + make_shared( + *get>(f) + ) + ); + else + _globalList.push_back( + make_shared( + *get>(f) + ) + ); + + // Reset explicit multiple overrides and mark all functions as implicit + for (auto& item: _globalList) + { + if (holds_alternative>(item)) + { + auto function = get>(item); + // Reset override history for past n-way merge + if (function->explicitOverride() && function->numOverriddenFromBases() > 1) + function->resetOverriddenBases(); + // Mark interface function as an implicit override + function->markImplicitOverride(); + } + else + { + auto function = get>(item); + // Reset override history for past n-way merge + if (function->explicitOverride() && function->numOverriddenFromBases() > 1) + function->resetOverriddenBases(); + if (!function->implicitOverride()) + function->markImplicitContractOverride(); + } + } +} + +void SolContract::explicitOverrides() +{ + for (auto& e: m_functions) + { + // All implicitly inherited interface functions must be explicitly inherited + // and implemented if not abstract. + if (holds_alternative>(e)) + { + auto t = get>(e); + if (t->implicitOverride()) + { + auto pos = find(m_functions.begin(), m_functions.end(), e); + auto contractOverride = make_shared( + SolContractFunction( + {name()}, + t->mutability(), + name(), + t->name(), + SolContractFunction::Type::EXPLICITOVERRIDEINTERFACE, + abstract() ? coinToss() : true, + coinToss(), + newReturnValue() + ) + ); + m_functions.erase(pos); + m_functions.insert(pos, contractOverride); + } + } + else + { + auto t = get>(e); + if (t->implicitOverride() && t->isVirtual()) + { + // If not abstract, all unimplemented functions must be implemented + if (!abstract()) + { + if (!t->implemented()) + { + // This function must be from an abstract contract + // Create a new contract function with an implementation + auto contractOverride = make_shared( + SolContractFunction( + {name()}, + t->mutability(), + name(), + t->name(), + SolContractFunction::Type::EXPLICITOVERRIDECONTRACT, + true, + coinToss(), + newReturnValue(), + t->visibility() + ) + ); + auto pos = find(m_functions.begin(), m_functions.end(), e); + m_functions.erase(pos); + m_functions.insert(pos, contractOverride); + } + // Implemented virtual base contract function + else + { + if (coinToss()) + { + // Reimplement + auto contractOverride = make_shared( + SolContractFunction( + {name()}, + t->mutability(), + name(), + t->name(), + SolContractFunction::Type::EXPLICITOVERRIDECONTRACT, + true, + coinToss(), + newReturnValue(), + t->visibility() + ) + ); + auto pos = find(m_functions.begin(), m_functions.end(), e); + m_functions.erase(pos); + m_functions.insert(pos, contractOverride); + } + } + } + // Abstract contract + else + { + // Virtual implemented base + if (t->implemented()) + { + // If we are explicitly overriding an implemented contract function + // we must implement it. + if (coinToss()) + { + auto contractOverride = make_shared( + SolContractFunction( + {name()}, + t->mutability(), + name(), + t->name(), + SolContractFunction::Type::EXPLICITOVERRIDECONTRACT, + true, + coinToss(), + newReturnValue(), + t->visibility() + ) + ); + auto pos = find(m_functions.begin(), m_functions.end(), e); + m_functions.erase(pos); + m_functions.insert(pos, contractOverride); + } + } + // Unimplemented base contract function virtual implicit + else + { + // Option 1: do nothing + // Option 2: override and virtualize and not implement + // Option 3: override and implement and optionally virtualize + switch (randomNumber() % 3) + { + case 0: + break; + case 1: + { + auto contractOverride = make_shared( + SolContractFunction( + {name()}, + t->mutability(), + name(), + t->name(), + SolContractFunction::Type::EXPLICITOVERRIDECONTRACT, + false, + true, + "", + t->visibility() + ) + ); + auto pos = find(m_functions.begin(), m_functions.end(), e); + m_functions.erase(pos); + m_functions.insert(pos, contractOverride); + break; + } + case 2: + { + auto contractOverride = make_shared( + SolContractFunction( + {name()}, + t->mutability(), + name(), + t->name(), + SolContractFunction::Type::EXPLICITOVERRIDECONTRACT, + true, + coinToss(), + newReturnValue(), + t->visibility() + ) + ); + auto pos = find(m_functions.begin(), m_functions.end(), e); + m_functions.erase(pos); + m_functions.insert(pos, contractOverride); + break; + } + } + } + } + } + } + } +} + +void SolContract::merge() +{ + /* Merge algorithm: + * 1. Deep copy all base interface functions (local) into a single list (global) + * 2. Mark all of these as implicit overrides + * 3. Iterate list of implicit overrides + * 3a. If n-way merge is necessary, do so and mark a two-base explicit override and add to contract + * 3b. If n-way merge is not possible, add as implicit override to contract + * 4. Iterate list of contract implicit and explicit (2-way) overrides + * 4a. If implicit, pseudo randomly mark it explicit + */ + FunctionList global{}; + // Step 1-2 + for (auto& base: m_baseContracts) + { + if (m_functionIndex < base->functionIndex()) + m_functionIndex = base->functionIndex(); + copyFunctions(base, global); + } + + // Step 3 + set> processedContractFunctionPair; + for (auto& functionInBaseContract: global) + { + if (contractFunction(functionInBaseContract)) + { + if (processedContractFunctionPair.count(pair(getContractFunction(functionInBaseContract)->m_contractName, getContractFunction(functionInBaseContract)->name()))) + continue; + else + processedContractFunctionPair.insert(pair(getContractFunction(functionInBaseContract)->m_contractName, getContractFunction(functionInBaseContract)->name())); + } + else + { + if (processedContractFunctionPair.count(pair(getInterfaceFunction(functionInBaseContract)->m_contractName, getInterfaceFunction(functionInBaseContract)->name()))) + continue; + else + processedContractFunctionPair.insert(pair(getInterfaceFunction(functionInBaseContract)->m_contractName, getInterfaceFunction(functionInBaseContract)->name())); + } + bool merged = false; + for (auto& functionInContract: m_functions) + { + merged = mergeFunctionBasetoContract(functionInBaseContract, functionInContract); + if (merged) + break; + } + if (!merged) + m_functions.push_back(functionInBaseContract); + } + // Step 4 + explicitOverrides(); + // Step 5 + if (!abstract()) + for (auto &f: m_functions) + if (holds_alternative>(f)) + { + auto function = get>(f); + if (function->implemented() && (function->visibility() == SolFunctionVisibility::EXTERNAL || function->visibility() == SolFunctionVisibility::PUBLIC)) + m_contractFunctionMap.insert(pair(function->name(), function->returnValue())); + } +} + +void SolContract::addBases(Contract const& _contract) +{ + vector> cyclicBaseList; + shared_ptr base; + for (auto &b: _contract.bases()) + { + shared_ptr cyclicBase; + switch (b.contract_or_interface_oneof_case()) + { + case ContractOrInterface::kC: + if (!cyclicBaseList.empty()) + cyclicBase = cyclicBaseList[random() % cyclicBaseList.size()]; + base = make_shared( + SolBaseContract(&b.c(), newBaseName(), cyclicBase, m_prng) + ); + break; + case ContractOrInterface::kI: + if (!cyclicBaseList.empty()) + { + auto tmp = cyclicBaseList[random() % cyclicBaseList.size()]; + if (tmp->type() == SolBaseContract::INTERFACE) + cyclicBase = tmp; + } + base = make_shared( + SolBaseContract(&b.i(), newBaseName(), cyclicBase, m_prng) + ); + break; + case ContractOrInterface::CONTRACT_OR_INTERFACE_ONEOF_NOT_SET: + continue; + } + m_baseContracts.push_back(base); + cyclicBaseList.push_back(base); + if (base->atleastOneBase()) + cyclicBaseList.push_back(base->randomBase()); + m_lastBaseName = base->lastBaseName(); + } + merge(); +} + +string SolContract::str() +{ + ostringstream bases; + for (auto &b: m_baseContracts) + if (!m_cyclicBase || b != m_cyclicBase) + bases << b->str(); + + ostringstream functions; + + // Print functions + for (auto &f: m_functions) + if (holds_alternative>(f)) + functions << get>(f)->str(); + + return Whiskers(R"( + +abstract contract is { + +})") + ("bases", bases.str()) + ("isAbstract", abstract()) + ("contractName", name()) + ("inheritance", !m_baseContracts.empty()) + ("baseNames", baseNames()) + ("functions", functions.str()) + .render(); +} + +bool SolContract::atleastOneBase() +{ + return !m_baseContracts.empty(); +} + +SolContract::SolContract( + Contract const& _contract, + std::string _name, + shared_ptr _cyclicBase, + shared_ptr _prng +) +{ + m_prng = _prng; + m_contractName = _name; + m_lastBaseName = m_contractName; + m_abstract = _contract.abstract(); + if (_cyclicBase) + { + m_cyclicBase = _cyclicBase; +#if 0 + std::cout << "Adding cyclic contract base " << _cyclicBase->name() << " to " << name() << std::endl; +#endif + m_baseContracts.push_back(_cyclicBase); + } + // Initialize function map. + m_contractFunctionMap = map{}; + addBases(_contract); + addFunctions(_contract); +} + +// Library implementation +void SolLibrary::addFunction(LibraryFunction const& _function) +{ + // Register function name and return value + string functionName = newFunctionName(); + string outputStr = newReturnValue(); + + SolFunctionVisibility visibility = visibilityConverter(_function.vis()); + + if (visibility == SolFunctionVisibility::PUBLIC || visibility == SolFunctionVisibility::EXTERNAL) + { + solAssert(!m_publicFunctionMap.count(functionName), "Sol proto adapter: Duplicate library function"); + m_publicFunctionMap.insert(pair(functionName, outputStr)); + } + + // Create and add function to library + m_functions.push_back( + make_unique( + SolLibraryFunction(_function, + name(), + functionName, + outputStr + ) + ) + ); +} + +SolLibrary::SolLibrary(Library const& _library, string _name, shared_ptr _prng) +{ + m_libraryName = _name; + m_prng = _prng; + for (LibraryFunction const& f: _library.funcdef()) + addFunction(f); +} + +string SolLibrary::str() const +{ + ostringstream functions; + + for (auto &f: m_functions) + functions << f->str(); + + return Whiskers(R"( +library { + +})") + ("name", name()) + ("functions", functions.str()) + .render(); +} + +bool SolLibrary::validTest() const +{ + return !m_publicFunctionMap.empty(); +} + +pair SolLibrary::pseudoRandomTest() +{ + solAssert(!m_publicFunctionMap.empty(), "Sol proto adaptor: Empty library map"); + string chosenFunction; + unsigned numFunctions = m_publicFunctionMap.size(); + unsigned functionIndex = randomNumber() % numFunctions; + unsigned mapIdx = 0; + for (auto &e: m_publicFunctionMap) + { + if (functionIndex == mapIdx) + { + chosenFunction = e.first; + break; + } + mapIdx++; + } + solAssert(m_publicFunctionMap.count(chosenFunction), "Sol proto adaptor: Invalid library function chosen"); + return pair(chosenFunction, m_publicFunctionMap[chosenFunction]); +} \ No newline at end of file diff --git a/test/tools/ossfuzz/SolProtoAdaptor.h b/test/tools/ossfuzz/SolProtoAdaptor.h new file mode 100644 index 000000000..5ffce876f --- /dev/null +++ b/test/tools/ossfuzz/SolProtoAdaptor.h @@ -0,0 +1,607 @@ +/* + 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 + +#include +#include +#include +#include +#include + +namespace solidity::test::solprotofuzzer::adaptor +{ + +/// Forward declarations +/// Solidity contract abstraction class +struct SolContract; +/// Solidity interface abstraction class +struct SolInterface; +/// Solidity library abstraction class +struct SolLibrary; +/// Solidity interface function abstraction class +struct SolInterfaceFunction; +/// Solidity contract function abstraction class +struct SolContractFunction; +/// Solidity library function abstraction class +struct SolLibraryFunction; +/// Variant type that points to one of contract, interface protobuf messages +using ProtoBaseContract = std::variant; + +enum class SolFunctionVisibility +{ + PUBLIC, + PRIVATE, + INTERNAL, + EXTERNAL +}; + +enum class SolFunctionStateMutability +{ + PURE, + VIEW, + PAYABLE, + NONPAYABLE +}; + +enum class SolLibraryFunctionStateMutability +{ + PURE, + VIEW, + NONPAYABLE +}; + +struct SolInterfaceFunction +{ + enum class Type + { + MEMBERFUNCTION, + IMPLICITOVERRIDE, + EXPLICITOVERRIDE + }; + + SolInterfaceFunction( + std::string _functionName, + SolFunctionStateMutability _mutability, + Type _type, + std::string _contractName + ); + bool operator==(SolInterfaceFunction const& _rhs) const; + bool operator!=(SolInterfaceFunction const& _rhs) const; + bool operator==(SolContractFunction const& _rhs) const; + bool operator!=(SolContractFunction const& _rhs) const; + void merge(SolInterfaceFunction const& _rhs); + bool namesake(SolInterfaceFunction const& _rhs) const; + bool namesake(SolContractFunction const& _rhs) const; + void markExplicitOverride(std::string _contractName); + + std::string overriddenFromBaseNames() const; + unsigned numOverriddenFromBases() const + { + return m_overriddenFrom.size(); + } + void resetOverriddenBases() + { + m_overriddenFrom.clear(); + m_overriddenFrom.push_back(m_contractName); + } + + std::string str() const; + + std::string name() const + { + return m_functionName; + } + SolFunctionStateMutability mutability() const + { + return m_mutability; + } + bool explicitOverride() const + { + return m_type == Type::EXPLICITOVERRIDE; + } + bool implicitOverride() const + { + return m_type == Type::IMPLICITOVERRIDE; + } + bool memberFunction() const + { + return m_type == Type::MEMBERFUNCTION; + } + void markImplicitOverride() + { + m_type = Type::IMPLICITOVERRIDE; + } + bool multipleBases() const + { + return m_overriddenFrom.size() > 1; + } + + std::string m_functionName; + SolFunctionStateMutability m_mutability = SolFunctionStateMutability::PURE; + std::vector m_overriddenFrom; + std::string m_contractName; + Type m_type; +}; + +struct SolContractFunction +{ + enum class Type + { + MEMBERFUNCTION, + IMPLICITOVERRIDECONTRACT, + IMPLICITOVERRIDEINTERFACE, + EXPLICITOVERRIDECONTRACT, + EXPLICITOVERRIDEINTERFACE + }; + + SolContractFunction( + ContractFunction const& _function, + std::string _contractName, + std::string _functionName, + Type _type, + bool _implement, + std::string _returnValue + ); + SolContractFunction( + std::vector _overriddenFrom, + SolFunctionStateMutability _mutability, + std::string _contractName, + std::string _functionName, + Type _type, + bool _implement, + bool _virtual, + std::string _returnValue, + SolFunctionVisibility _vis = SolFunctionVisibility::EXTERNAL + ); + + bool memberFunction() const + { + return m_type == Type::MEMBERFUNCTION; + } + bool explicitInterfaceOverride() const + { + return m_type == Type::EXPLICITOVERRIDEINTERFACE; + } + bool explicitContractOverride() const + { + return m_type == Type::EXPLICITOVERRIDECONTRACT; + } + bool implicitInterfaceOverride() const + { + return m_type == Type::IMPLICITOVERRIDEINTERFACE; + } + bool implicitContractOverride() const + { + return m_type == Type::IMPLICITOVERRIDECONTRACT; + } + bool explicitOverride() const + { + return explicitContractOverride() || explicitInterfaceOverride(); + } + bool implicitOverride() const + { + return implicitContractOverride() || implicitInterfaceOverride(); + } + void markImplicitContractOverride() + { + m_type = Type::IMPLICITOVERRIDECONTRACT; + } + void markImplicitInterfaceOverride() + { + m_type = Type::IMPLICITOVERRIDEINTERFACE; + } + void markExplicitContractOverride(std::string _contractName) + { + m_type = Type::EXPLICITOVERRIDECONTRACT; + m_contractName = _contractName; + m_overriddenFrom.clear(); + m_overriddenFrom.push_back(m_contractName); + } + void markExplicitInterfaceOverride(std::string _contractName) + { + m_type = Type::EXPLICITOVERRIDEINTERFACE; + m_contractName = _contractName; + m_overriddenFrom.clear(); + m_overriddenFrom.push_back(m_contractName); + } + std::string overriddenFromBaseNames() const; + unsigned numOverriddenFromBases() const + { + return m_overriddenFrom.size(); + } + void resetOverriddenBases() + { + m_overriddenFrom.clear(); + m_overriddenFrom.push_back(m_contractName); + } + + bool namesake(SolContractFunction const& _rhs) const; + bool namesake(SolInterfaceFunction const& _rhs) const; + bool operator==(SolContractFunction const& _rhs) const; + bool operator!=(SolContractFunction const& _rhs) const; + bool operator==(SolInterfaceFunction const& _rhs) const; + bool operator!=(SolInterfaceFunction const& _rhs) const; + void merge(SolContractFunction const& _rhs); + void merge(SolInterfaceFunction const& _rhs); + bool disallowed() const; + std::string str() const; + + std::string name() const + { + return m_functionName; + } + std::string contractName() const + { + return m_contractName; + } + bool isVirtual() const + { + return m_virtual; + } + bool implemented() const + { + return m_implemented; + } + std::string returnValue() const + { + return m_returnValue; + } + SolFunctionVisibility visibility() const + { + return m_visibility; + } + SolFunctionStateMutability mutability() const + { + return m_mutability; + } + + std::string m_contractName; + std::string m_functionName; + SolFunctionVisibility m_visibility = SolFunctionVisibility::PUBLIC; + SolFunctionStateMutability m_mutability = SolFunctionStateMutability::PURE; + bool m_virtual = false; + std::string m_returnValue; + bool m_implemented = true; + Type m_type; + std::vector m_overriddenFrom; +}; + +struct SolLibraryFunction +{ + SolLibraryFunction( + LibraryFunction const& _function, + std::string _libraryName, + std::string _functionName, + std::string _returnValue + ); + std::string str() const; + + std::string name() const + { + return m_functionName; + } + std::string returnValue() const + { + return m_returnValue; + } + SolFunctionVisibility visibility() const + { + return m_visibility; + } + SolLibraryFunctionStateMutability mutability() const + { + return m_mutability; + } + + std::string m_libraryName; + std::string m_functionName; + SolFunctionVisibility m_visibility = SolFunctionVisibility::PUBLIC; + SolLibraryFunctionStateMutability m_mutability = SolLibraryFunctionStateMutability::PURE; + std::string m_returnValue; +}; + +struct SolLibrary +{ + SolLibrary(Library const& _library, std::string _name, std::shared_ptr _prng); + std::vector> m_functions; + /// Maps publicly exposed function name to expected output + std::map m_publicFunctionMap; + + void addFunction(LibraryFunction const& _function); + + bool validTest() const; + unsigned randomNumber() const + { + return m_prng->operator()(); + } + + /// Returns a pair of function name and expected output + /// that is pseudo randomly chosen from the list of all + /// library functions. + std::pair pseudoRandomTest(); + + std::string str() const; + + std::string name() const + { + return m_libraryName; + } + std::string newFunctionName() + { + return "f" + std::to_string(m_functionIndex++); + } + std::string newReturnValue() + { + return std::to_string(random()); + } + + std::string m_libraryName; + unsigned m_functionIndex = 0; + unsigned m_returnValue = 0; + std::shared_ptr m_prng; +}; + +struct SolBaseContract +{ + enum BaseType + { + INTERFACE, + CONTRACT + }; + + SolBaseContract( + ProtoBaseContract _base, + std::string _name, + std::shared_ptr _prng + ); + + SolBaseContract( + ProtoBaseContract _base, + std::string _name, + std::shared_ptr _cyclicBase, + std::shared_ptr _prng + ); + + SolBaseContract(std::shared_ptr _base); + + BaseType type() const; + std::string name(); + std::string str(); + std::shared_ptr interface() + { + return std::get>(m_base); + } + std::shared_ptr contract() + { + return std::get>(m_base); + } + std::shared_ptr randomBase(); + bool atleastOneBase(); + + unsigned functionIndex(); + std::string lastBaseName(); + + std::variant, std::shared_ptr> m_base; +}; + +struct SolContract +{ + enum class Function + { + INTERFACE, + CONTRACT + }; + + using FunctionType = std::variant, std::shared_ptr>; + using FunctionList = std::vector; + + SolContract( + Contract const& _contract, + std::string _name, + std::shared_ptr _cyclicBase, + std::shared_ptr _prng + ); + + bool mergeFunctionBasetoContract(FunctionType _f1, FunctionType _f2); + void explicitOverrides(); + template + bool requiresMerge(T1 _f1, T2 _f2) + { + return _f1->namesake(*_f2); + } + std::shared_ptr randomBase() + { + return m_baseContracts[randomNumber() % m_baseContracts.size()]; + } + + bool atleastOneBase(); + void merge(); + std::string str(); + void addFunctions(Contract const& _contract); + void addBases(Contract const& _contract); + void copyFunctions(std::shared_ptr _base, FunctionList& _globalList); + + bool validTest(); + std::string baseNames() const; + void validContractTests( + std::map>& _testSet + ); + + unsigned randomNumber() const + { + return m_prng->operator()(); + } + bool coinToss() const + { + return randomNumber() % 2 == 0; + } + + std::string name() const + { + return m_contractName; + } + bool abstract() const + { + return m_abstract; + } + std::string newFunctionName() + { + return "f" + std::to_string(m_functionIndex++); + } + unsigned functionIndex() const + { + return m_functionIndex; + } + std::string newBaseName() + { + m_lastBaseName += "B"; + return m_lastBaseName; + } + std::string lastBaseName() const + { + return m_lastBaseName; + } + std::string newReturnValue() + { + return std::to_string(randomNumber()); + } + Function functionType(FunctionType _type) + { + if (std::holds_alternative>(_type)) + return Function::CONTRACT; + else + return Function::INTERFACE; + } + bool contractFunction(FunctionType _type) + { + return functionType(_type) == Function::CONTRACT; + } + bool interfaceFunction(FunctionType _type) + { + return functionType(_type) == Function::INTERFACE; + } + std::shared_ptr getContractFunction(FunctionType _type) + { + return std::get>(_type); + } + std::shared_ptr getInterfaceFunction(FunctionType _type) + { + return std::get>(_type); + } + + std::string m_contractName; + bool m_abstract = false; + unsigned m_functionIndex = 0; + unsigned m_returnValue = 0; + std::string m_lastBaseName; + FunctionList m_functions; + std::vector> m_baseContracts; + /// Maps non abstract contract name to list of publicly exposed function name + /// and their expected output + std::map m_contractFunctionMap; + std::shared_ptr m_prng; + std::shared_ptr m_cyclicBase; +}; + +struct SolInterface +{ + SolInterface( + Interface const& _interface, + std::string _interfaceName, + std::shared_ptr _cyclicBase, + std::shared_ptr _prng + ); + + void merge(); + + std::string name() const + { + return m_interfaceName; + } + + unsigned randomNumber() const + { + return m_prng->operator()(); + } + + bool coinToss() const + { + return randomNumber() % 2 == 0; + } + + bool atleastOneBase() + { + return !m_baseInterfaces.empty(); + } + + std::shared_ptr randomBase() + { + return m_baseInterfaces[randomNumber() % m_baseInterfaces.size()]; + } + + std::string newFunctionName() + { + return "f" + std::to_string(m_functionIndex++); + } + + unsigned functionIndex() const + { + return m_functionIndex; + } + + std::string newBaseName() + { + m_lastBaseName += "B"; + return m_lastBaseName; + } + + std::string lastBaseName() + { + return m_lastBaseName; + } + + std::string str() const; + + /// Returns the Solidity code for all base interfaces + /// inherited by this interface. + std::string baseInterfaceStr() const; + std::string functionStr() const; + bool bases() const + { + return m_baseInterfaces.size() > 0; + } + + /// Returns comma-space separated names of base interfaces inherited by + /// this interface. + std::string baseNames() const; + + /// Add base contracts in a depth-first manner + void addBases(Interface const& _interface); + /// Add functions + void addFunctions(Interface const& _interface); + + unsigned m_functionIndex = 0; + std::string m_lastBaseName; + std::string m_interfaceName; + std::vector> m_functions; + std::vector> m_baseInterfaces; + std::shared_ptr m_cyclicBaseInterface; + std::shared_ptr m_prng; +}; +} diff --git a/test/tools/ossfuzz/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp index 1c422ef8c..96d31119c 100644 --- a/test/tools/ossfuzz/protoToAbiV2.cpp +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -704,7 +704,7 @@ void TypeVisitor::structDefinition(StructType const& _type) // Commence struct declaration string structDef = lineString( "struct " + - string(s_structNamePrefix) + + m_structPrefix + to_string(m_structCounter) + " {" ); @@ -718,7 +718,7 @@ void TypeVisitor::structDefinition(StructType const& _type) if (!ValidityVisitor().visit(t)) continue; - TypeVisitor tVisitor(m_structCounter + 1); + TypeVisitor tVisitor(m_structCounter + 1, m_indentation - 1, m_structPrefix); type = tVisitor.visit(t); m_structCounter += tVisitor.numStructs(); m_structDef << tVisitor.structDef(); @@ -749,7 +749,7 @@ string TypeVisitor::visit(StructType const& _type) // Set last dyn param if struct contains a dyn param e.g., bytes, array etc. m_isLastDynParamRightPadded = DynParamVisitor().visit(_type); // If top-level struct is a non-emtpy struct, assign the name S - m_baseType = s_structTypeName + to_string(m_structStartCounter); + m_baseType = m_structPrefix + to_string(m_structStartCounter); } else m_baseType = {}; diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h index f75f5d7e4..f638e9743 100644 --- a/test/tools/ossfuzz/protoToAbiV2.h +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -152,7 +152,7 @@ public: ProtoConverter(ProtoConverter const&) = delete; ProtoConverter(ProtoConverter&&) = delete; std::string contractToString(Contract const& _input); -private: + enum class Delimiter { ADD, @@ -205,7 +205,7 @@ private: /// Solidity code to be placed inside test function scope. template std::pair processType(T const& _type, bool _isValueType); - +private: /// Convert a protobuf type @a _T into Solidity variable assignment and check /// statements to be placed inside contract and test function scopes. /// @param: _varName is the name of the Solidity variable @@ -348,6 +348,8 @@ private: return s_paramNamePrefix; case Contract_Test::Contract_Test_RETURNDATA_CODER: return s_localVarNamePrefix; + default: + return s_localVarNamePrefix; } } @@ -375,6 +377,7 @@ private: std::ostringstream m_typedReturn; /// Argument names to be passed to coder functions std::ostringstream m_argsCoder; +public: /// Predicate that is true if we are in contract scope bool m_isStateVar; unsigned m_counter; @@ -588,12 +591,13 @@ private: class TypeVisitor: public AbiV2ProtoVisitor { public: - TypeVisitor(unsigned _structSuffix = 0): - m_indentation(1), + TypeVisitor(unsigned _structSuffix = 0, unsigned _indentation = 1, std::string _structPrefix = "S"): + m_indentation(_indentation), m_structCounter(_structSuffix), m_structStartCounter(_structSuffix), m_structFieldCounter(0), - m_isLastDynParamRightPadded(false) + m_isLastDynParamRightPadded(false), + m_structPrefix(_structPrefix) {} std::string visit(BoolType const&) override; @@ -649,8 +653,7 @@ private: unsigned m_structStartCounter; unsigned m_structFieldCounter; bool m_isLastDynParamRightPadded; - - static auto constexpr s_structTypeName = "S"; + std::string m_structPrefix; }; /// Returns a pair of strings, first of which contains assignment statements diff --git a/test/tools/ossfuzz/protoToSol.cpp b/test/tools/ossfuzz/protoToSol.cpp index 603911ae7..505c8ba7f 100644 --- a/test/tools/ossfuzz/protoToSol.cpp +++ b/test/tools/ossfuzz/protoToSol.cpp @@ -16,6 +16,7 @@ */ #include +#include #include @@ -24,8 +25,9 @@ #include using namespace solidity::test::solprotofuzzer; -using namespace solidity::util; +using namespace solidity::test::solprotofuzzer::adaptor; using namespace std; +using namespace solidity::util; string ProtoConverter::protoToSolidity(Program const& _p) { @@ -78,6 +80,7 @@ pair ProtoConverter::generateTestCase(TestContract const& _testC break; string contractName = testTuple.first; string contractVarName = "tc" + to_string(contractVarIndex); + Whiskers init(R"( = new ();)"); init("endl", "\n"); init("ind", "\t\t"); @@ -179,20 +182,72 @@ string ProtoConverter::visit(ContractType const& _contractType) string ProtoConverter::visit(Contract const& _contract) { + if (_contract.funcdef_size() == 0 && _contract.bases_size() == 0) + return ""; + openProgramScope(&_contract); - return ""; + try { + auto contract = SolContract(_contract, programName(&_contract), nullptr, m_randomGen); + if (contract.validTest()) + { + map> testSet; + contract.validContractTests(testSet); + for (auto &contractTestSet: testSet) + { + m_contractTests.insert(pair(contractTestSet.first, map{})); + for (auto &contractTest: contractTestSet.second) + m_contractTests[contractTestSet.first].insert( + make_pair(contractTest.first, contractTest.second) + ); + } + return contract.str(); + } + // There is no point in generating a contract that can not provide + // a valid test case, so we simply bail. + else + { + return ""; + } + } + // Catch exception thrown when input specification is invalid e.g. + // invalid inheritance hierarchy + catch (langutil::FuzzerError const& error) + { + // Return empty string if input specification is invalid. + return ""; + } } string ProtoConverter::visit(Interface const& _interface) { + if (_interface.funcdef_size() == 0 && _interface.bases_size() == 0) + return ""; + openProgramScope(&_interface); - return ""; + try { + auto interface = SolInterface(_interface, programName(&_interface), nullptr, m_randomGen); + return interface.str(); + } + catch (langutil::FuzzerError const& error) + { + // Return empty string if input specification is invalid. + return ""; + } } string ProtoConverter::visit(Library const& _library) { + if (emptyLibrary(_library)) + return ""; + openProgramScope(&_library); - return ""; + auto lib = SolLibrary(_library, programName(&_library), m_randomGen); + if (lib.validTest()) + { + auto libTestPair = lib.pseudoRandomTest(); + m_libraryTests.push_back({lib.name(), libTestPair.first, libTestPair.second}); + } + return lib.str(); } tuple ProtoConverter::pseudoRandomLibraryTest() diff --git a/test/tools/ossfuzz/protoToSol.h b/test/tools/ossfuzz/protoToSol.h index 6a534d71c..6d9b32674 100644 --- a/test/tools/ossfuzz/protoToSol.h +++ b/test/tools/ossfuzz/protoToSol.h @@ -111,6 +111,7 @@ struct SolRandomNumGenerator * } * } */ + class ProtoConverter { public: diff --git a/test/tools/ossfuzz/solProtoFuzzer.cpp b/test/tools/ossfuzz/solProtoFuzzer.cpp new file mode 100644 index 000000000..7939135e5 --- /dev/null +++ b/test/tools/ossfuzz/solProtoFuzzer.cpp @@ -0,0 +1,275 @@ +/* + 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 + +static evmc::VM evmone = evmc::VM{evmc_create_evmone()}; + +using namespace solidity; +using namespace solidity::test; +using namespace solidity::test::solprotofuzzer; +using namespace solidity::util; +using namespace solidity::test::abiv2fuzzer; +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); +} + +/// Compares two runs of EVMC returing true if they are +/// equal and false otherwise. +#if 0 +bool isDifferentialOutputExpected(evmc::result const& _run1, evmc::result const& _run2) +{ + if (_run1.output_size != _run2.output_size) + return false; + + if (getenv("SOL_DEBUG_FILE") != nullptr) + { + unsigned r = _run1.output_data[31] | _run1.output_data[30] << 8 | + _run1.output_data[29] << 16 | _run1.output_data[28] << 24; + std::cout << r << std::endl; + } + + return (memcmp(_run1.output_data, _run2.output_data, _run1.output_size) == 0); +} +#endif + +/// 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::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 compileContract( + std::string _sourceCode, + std::string _contractName, + std::map 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 (evmasm::StackTooDeepException const&) + { + return std::make_pair(bytes{}, Json::Value(0)); + } +} + +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 _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 = converter.protoToSolidity(_input); + + 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(ifstr), + std::istreambuf_iterator() + }; + std::cout << sol_source << std::endl; + } + + auto minimalResult = compileDeployAndExecute( + sol_source, + ":C", + "test()", + frontend::OptimiserSettings::minimal(), + converter.libraryTest() ? converter.libraryName() : "" + ); + bool successState = minimalResult.status_code == EVMC_SUCCESS; + if (successState) + solAssert( + isOutputExpected(minimalResult), + "Proto solc fuzzer: Output incorrect" + ); + +// auto optResult = compileDeployAndExecute( +// sol_source, +// ":C", +// "test()", +// frontend::OptimiserSettings::standard(), +// converter.libraryTest() ? converter.libraryName() : "" +// ); + + // Both executions should either return success or identical evmone status code +// bool successState = minimalResult.status_code == EVMC_SUCCESS && optResult.status_code == EVMC_SUCCESS; +// bool identicalState = minimalResult.status_code == optResult.status_code; +// bool executeState = successState || identicalState; +// solAssert(executeState, "Proto solc fuzzer: Different execution status"); + +// if (successState) +// solAssert( +// isDifferentialOutputExpected(minimalResult, optResult), +// "Proto solc fuzzer: Output mismatch" +// ); +// +// return; +} \ No newline at end of file