diff --git a/test/tools/ossfuzz/AbiV2IsabelleFuzzer.cpp b/test/tools/ossfuzz/AbiV2IsabelleFuzzer.cpp index e1310ea11..4e28c444b 100644 --- a/test/tools/ossfuzz/AbiV2IsabelleFuzzer.cpp +++ b/test/tools/ossfuzz/AbiV2IsabelleFuzzer.cpp @@ -28,10 +28,28 @@ using namespace std; static constexpr size_t abiCoderHeapSize = 1024 * 512; -DEFINE_PROTO_FUZZER(Contract const&) +DEFINE_PROTO_FUZZER(Contract const& _contract) { + ProtoConverter converter; + string contractSource = converter.contractToString(_contract); + + if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) + { + // With libFuzzer binary run this to generate the solidity source file x.sol from a proto input: + // PROTO_FUZZER_DUMP_PATH=x.sol ./a.out proto-input + ofstream of(dump_path); + of << contractSource; + } + + string typeString = converter.isabelleTypeString(); + string valueString = converter.isabelleValueString(); + std::cout << typeString << std::endl; + std::cout << valueString << std::endl; abicoder::ABICoder coder(abiCoderHeapSize); - auto [encodeStatus, encodedData] = coder.encode("bool", "true"); - solAssert(encodeStatus, "Isabelle abicoder fuzzer: Encoding failed"); + if (!typeString.empty()) + { + auto [encodeStatus, encodedData] = coder.encode(typeString, valueString); + solAssert(encodeStatus, "Isabelle abicoder fuzzer: Encoding failed"); + } return; } \ No newline at end of file diff --git a/test/tools/ossfuzz/abiV2Proto.proto b/test/tools/ossfuzz/abiV2Proto.proto index b6640bbe9..886ca53f7 100644 --- a/test/tools/ossfuzz/abiV2Proto.proto +++ b/test/tools/ossfuzz/abiV2Proto.proto @@ -32,10 +32,8 @@ message FixedByteType { required uint32 width = 1; } -// address, address payable -message AddressType { - required bool payable = 1; -} +// address +message AddressType {} message ValueType { oneof value_type_oneof { diff --git a/test/tools/ossfuzz/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp index cee5a1cf7..1fac9730a 100644 --- a/test/tools/ossfuzz/protoToAbiV2.cpp +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -1,6 +1,7 @@ #include #include +#include /// Convenience macros /// Returns a valid Solidity integer width w such that 8 <= w <= 256. @@ -315,6 +316,10 @@ pair ProtoConverter::assignChecker( m_counter += acVisitor.counted(); m_checks << assignCheckStrPair.second; + appendToIsabelleValueString( + acVisitor.isabelleValueString(), + ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) + ); // State variables cannot be assigned in contract-scope // Therefore, we buffer their assignments and @@ -333,12 +338,12 @@ std::string ProtoConverter::equalityChecksAsString() return m_checks.str(); } -std::string ProtoConverter::delimiterToString(Delimiter _delimiter) +std::string ProtoConverter::delimiterToString(Delimiter _delimiter, bool _space) { switch (_delimiter) { case Delimiter::ADD: - return ", "; + return _space ? ", " : ","; case Delimiter::SKIP: return ""; } @@ -448,7 +453,15 @@ void ProtoConverter::appendToIsabelleTypeString( Delimiter _delimiter ) { - m_isabelleTypeString << delimiterToString(_delimiter) << _typeString; + m_isabelleTypeString << delimiterToString(_delimiter, false) << _typeString; +} + +void ProtoConverter::appendToIsabelleValueString( + std::string const& _valueString, + Delimiter _delimiter +) +{ + m_isabelleValueString << delimiterToString(_delimiter, false) << _valueString; } std::string ProtoConverter::typedParametersAsString(CalleeType _calleeType) @@ -736,6 +749,15 @@ string ProtoConverter::isabelleTypeString() const return typeString; } +string ProtoConverter::isabelleValueString() const +{ + string valueString = m_isabelleValueString.str(); + if (!valueString.empty()) + return "(" + valueString + ")"; + else + return valueString; +} + string ProtoConverter::contractToString(Contract const& _input) { visit(_input); @@ -777,9 +799,9 @@ string TypeVisitor::visit(FixedByteType const& _type) return m_baseType; } -string TypeVisitor::visit(AddressType const& _type) +string TypeVisitor::visit(AddressType const&) { - m_baseType = getAddressTypeAsString(_type); + m_baseType = "address"; m_structTupleString.addTypeStringToTuple(m_baseType); return m_baseType; } @@ -890,33 +912,50 @@ string TypeVisitor::visit(StructType const& _type) } /// AssignCheckVisitor implementation +void AssignCheckVisitor::ValueStream::appendValue(string& _value) +{ + solAssert(!_value.empty(), "Abiv2 fuzzer: Empty value"); + index++; + if (index > 1) + stream << ","; + stream << _value; +} + pair AssignCheckVisitor::visit(BoolType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); + m_valueStream.appendValue(value); return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); } pair AssignCheckVisitor::visit(IntegerType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); + m_valueStream.appendValue(value); return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); } pair AssignCheckVisitor::visit(FixedByteType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); + string isabelleValue = ValueGetterVisitor{}.isabelleBytesValueAsString(value); + m_valueStream.appendValue(isabelleValue); return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); } pair AssignCheckVisitor::visit(AddressType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); + string isabelleValue = ValueGetterVisitor{}.isabelleAddressValueAsString(value); + m_valueStream.appendValue(isabelleValue); return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); } pair AssignCheckVisitor::visit(DynamicByteArrayType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); + string isabelleValue = ValueGetterVisitor{}.isabelleBytesValueAsString(value); + m_valueStream.appendValue(isabelleValue); DataType dataType = _type.type() == DynamicByteArrayType::BYTES ? DataType::BYTES : DataType::STRING; return assignAndCheckStringPair(m_varName, m_paramName, value, value, dataType); } @@ -981,6 +1020,7 @@ pair AssignCheckVisitor::visit(ArrayType const& _type) pair assignCheckBuffer; string wasVarName = m_varName; string wasParamName = m_paramName; + m_valueStream.startArray(); for (unsigned i = 0; i < length; i++) { m_varName = wasVarName + "[" + to_string(i) + "]"; @@ -991,6 +1031,7 @@ pair AssignCheckVisitor::visit(ArrayType const& _type) if (i < length - 1) m_structCounter = wasStructCounter; } + m_valueStream.endArray(); // Since struct visitor won't be called for zero-length // arrays, struct counter will not get incremented. Therefore, @@ -1022,6 +1063,7 @@ pair AssignCheckVisitor::visit(StructType const& _type) string wasVarName = m_varName; string wasParamName = m_paramName; + m_valueStream.startStruct(); for (auto const& t: _type.t()) { m_varName = wasVarName + ".m" + to_string(i); @@ -1035,6 +1077,7 @@ pair AssignCheckVisitor::visit(StructType const& _type) assignCheckBuffer.second += assign.second; i++; } + m_valueStream.endStruct(); m_varName = wasVarName; m_paramName = wasParamName; return assignCheckBuffer; @@ -1117,11 +1160,11 @@ string ValueGetterVisitor::visit(AddressType const&) return addressValueAsString(counter()); } -string ValueGetterVisitor::visit(DynamicByteArrayType const& _type) +string ValueGetterVisitor::visit(DynamicByteArrayType const&) { return bytesArrayValueAsString( counter(), - getDataTypeOfDynBytesType(_type) == DataType::BYTES + true ); } @@ -1195,10 +1238,29 @@ std::string ValueGetterVisitor::fixedByteValueAsString(unsigned _width, unsigned std::string ValueGetterVisitor::addressValueAsString(unsigned _counter) { - // TODO: Isabelle encoder expects address literal to be exactly + return "address(" + maskUnsignedIntToHex(_counter, 40) + ")"; +} + +std::string ValueGetterVisitor::isabelleAddressValueAsString(std::string& _solAddressString) +{ + // Isabelle encoder expects address literal to be exactly // 20 bytes and a hex string. // Example: 0x0102030405060708090a0102030405060708090a - return "address(" + maskUnsignedIntToHex(_counter, 40) + ")"; + std::regex const addressPattern("address\\((.*)\\)"); + std::smatch match; + solAssert(std::regex_match(_solAddressString, match, addressPattern), "Abiv2 fuzzer: Invalid address string"); + std::string addressHex = match[1].str(); + addressHex.erase(2, 24); + return addressHex; +} + +std::string ValueGetterVisitor::isabelleBytesValueAsString(std::string& _solBytesString) +{ + std::regex const bytesPattern("hex\"(.*)\""); + std::smatch match; + solAssert(std::regex_match(_solBytesString, match, bytesPattern), "Abiv2 fuzzer: Invalid bytes string"); + std::string bytesHex = match[1].str(); + return "0x" + bytesHex; } std::string ValueGetterVisitor::variableLengthValueAsString( diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h index 146e444a2..722dcbd58 100644 --- a/test/tools/ossfuzz/protoToAbiV2.h +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -153,6 +153,7 @@ public: ProtoConverter(ProtoConverter&&) = delete; std::string contractToString(Contract const& _input); std::string isabelleTypeString() const; + std::string isabelleValueString() const; private: enum class Delimiter { @@ -295,6 +296,13 @@ private: Delimiter _delimiter ); + /// Append @a _valueString to value string meant to be + /// passed to Isabelle coder API. + void appendToIsabelleValueString( + std::string const& _valueString, + Delimiter _delimiter + ); + /// Returns a Solidity variable declaration statement /// @param _type: string containing Solidity type of the /// variable to be declared. @@ -367,7 +375,7 @@ private: } /// Convert delimter to a comma or null string. - static std::string delimiterToString(Delimiter _delimiter); + static std::string delimiterToString(Delimiter _delimiter, bool _space = true); /// Contains the test program std::ostringstream m_output; @@ -379,6 +387,9 @@ private: std::ostringstream m_typedParamsPublic; /// Contains type string to be passed to Isabelle API std::ostringstream m_isabelleTypeString; + /// Contains values to be encoded in the format accepted + /// by the Isabelle API. + std::ostringstream m_isabelleValueString; /// Contains type stream to be used in returndata coder function /// signature std::ostringstream m_types; @@ -476,19 +487,6 @@ public: return "bytes" + std::to_string(getFixedByteWidth(_x)); } - static std::string getAddressTypeAsString(AddressType const& _x) - { - return (_x.payable() ? "address payable" : "address"); - } - - static DataType getDataTypeOfDynBytesType(DynamicByteArrayType const& _x) - { - if (_x.type() == DynamicByteArrayType::STRING) - return DataType::STRING; - else - return DataType::BYTES; - } - // Convert _counter to string and return its keccak256 hash static u256 hashUnsignedInt(unsigned _counter) { @@ -531,7 +529,13 @@ public: // this linear equation to make the number derived from // _counter approach a uniform distribution over // [0, s_maxDynArrayLength] - return (_counter + 879) * 32 % (s_maxDynArrayLength + 1); + auto v = (_counter + 879) * 32 % (s_maxDynArrayLength + 1); + /// Always return an even number because Isabelle string + /// values are formatted as hex literals + if (v % 2 == 1) + return v + 1; + else + return v; } static std::string bytesArrayTypeAsString(DynamicByteArrayType const& _x) @@ -658,10 +662,6 @@ private: { stream << ")"; } - std::string operator()() - { - return stream.str(); - } void addTypeStringToTuple(std::string& _typeString); void addArrayBracketToType(std::string& _arrayBracket); }; @@ -735,7 +735,35 @@ public: { return m_structCounter - m_structStart; } + + std::string isabelleValueString() + { + return m_valueStream.stream.str(); + } private: + struct ValueStream + { + ValueStream() = default; + unsigned index = 0; + std::ostringstream stream; + void startStruct() + { + stream << "("; + } + void endStruct() + { + stream << ")"; + } + void startArray() + { + stream << "["; + } + void endArray() + { + stream << "]"; + } + void appendValue(std::string& _value); + }; std::string indentation() { return std::string(m_indentation * 1, '\t'); @@ -764,6 +792,7 @@ private: bool m_stateVar; unsigned m_structCounter; unsigned m_structStart; + ValueStream m_valueStream; }; /// Returns a valid value (as a string) for a given type. @@ -786,6 +815,8 @@ public: solAssert(false, "ABIv2 proto fuzzer: Cannot call valuegettervisitor on complex type"); } using AbiV2ProtoVisitor::visit; + static std::string isabelleAddressValueAsString(std::string& _solAddressString); + static std::string isabelleBytesValueAsString(std::string& _solFixedBytesString); private: unsigned counter() {