diff --git a/test/tools/ossfuzz/abiV2Proto.proto b/test/tools/ossfuzz/abiV2Proto.proto index c634e545c..33f887587 100644 --- a/test/tools/ossfuzz/abiV2Proto.proto +++ b/test/tools/ossfuzz/abiV2Proto.proto @@ -92,8 +92,13 @@ message TestFunction { } message Contract { + enum Test { + CALLDATA_CODER = 1; + RETURNDATA_CODER = 2; + } required VarDecl state_vars = 1; required TestFunction testfunction = 2; + required Test test = 3; } package dev.test.abiv2fuzzer; \ No newline at end of file diff --git a/test/tools/ossfuzz/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp index 9415bdcd4..b980a6f93 100644 --- a/test/tools/ossfuzz/protoToAbiV2.cpp +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -114,7 +114,14 @@ template pair ProtoConverter::processType(T const& _type, bool _isValueType) { ostringstream local, global; - auto [varName, paramName] = newVarNames(getNextVarCounter()); + auto [varName, paramName] = newVarNames(getNextVarCounter(), m_isStateVar); + + // Add variable name to the argument list of coder function call + if (m_argsCoder.str().empty()) + m_argsCoder << varName; + else + m_argsCoder << ", " << varName; + string location{}; if (!m_isStateVar && !_isValueType) location = "memory"; @@ -177,6 +184,16 @@ pair ProtoConverter::varDecl( _paramName, ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) ); + appendTypes( + _isValueType, + typeStr, + ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) + ); + appendTypedReturn( + _isValueType, + typeStr, + ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) + ); // Update dyn param only if necessary if (tVisitor.isLastDynParamRightPadded()) @@ -259,6 +276,41 @@ void ProtoConverter::appendTypedParams( } } +void ProtoConverter::appendTypes( + bool _isValueType, + string const& _typeString, + Delimiter _delimiter +) +{ + string qualifiedTypeString = ( + _isValueType ? + _typeString : + _typeString + " memory" + ); + m_types << Whiskers(R"()") + ("delimiter", delimiterToString(_delimiter)) + ("type", qualifiedTypeString) + .render(); +} + +void ProtoConverter::appendTypedReturn( + bool _isValueType, + string const& _typeString, + Delimiter _delimiter +) +{ + string qualifiedTypeString = ( + _isValueType ? + _typeString : + _typeString + " memory" + ); + m_typedReturn << Whiskers(R"( )") + ("delimiter", delimiterToString(_delimiter)) + ("type", qualifiedTypeString) + ("varName", "lv_" + to_string(m_varCounter - 1)) + .render(); +} + // Adds the qualifier "calldata" to non-value parameter of an external function. void ProtoConverter::appendTypedParamsExternal( bool _isValueType, @@ -310,7 +362,6 @@ std::string ProtoConverter::typedParametersAsString(CalleeType _calleeType) } } -// Test function to be called externally. string ProtoConverter::visit(TestFunction const& _x, string const& _storageVarDefs) { // TODO: Support more than one but less than N local variables @@ -320,38 +371,76 @@ string ProtoConverter::visit(TestFunction const& _x, string const& _storageVarDe string localVarDefs = localVarBuffers.second; ostringstream testBuffer; - string functionDecl = "function test() public returns (uint)"; + + string testFunction = Whiskers(R"( + function test() public returns (uint) { + return test_calldata_coding(); + return test_returndata_coding(); + })") + ("calldata", m_test == Contract_Test::Contract_Test_CALLDATA_CODER) + ("returndata", m_test == Contract_Test::Contract_Test_RETURNDATA_CODER) + .render(); + + string functionDeclCalldata = "function test_calldata_coding() internal returns (uint)"; + string functionDeclReturndata = "function test_returndata_coding() internal returns (uint)"; + testBuffer << Whiskers(R"( - { + + + { - - })") + + } + + + + { + + } + + + function coder_returndata_external() external returns () { + + + return (); + } + +)") + ("testFunction", testFunction) + ("calldata", m_test == Contract_Test::Contract_Test_CALLDATA_CODER) + ("returndata", m_test == Contract_Test::Contract_Test_RETURNDATA_CODER) + ("calldataHelperFuncs", calldataHelperFunctions()) + ("varsPresent", !m_types.str().empty()) ("structTypeDecl", structTypeDecl) - ("functionDecl", functionDecl) + ("functionDeclCalldata", functionDeclCalldata) + ("functionDeclReturndata", functionDeclReturndata) ("storageVarDefs", _storageVarDefs) ("localVarDefs", localVarDefs) - ("testCode", testCode(_x.invalid_encoding_length())) + ("calldataTestCode", testCallDataFunction(_x.invalid_encoding_length())) + ("returndataTestCode", testReturnDataFunction()) + ("return_types", m_types.str()) + ("return_values", m_argsCoder.str()) .render(); return testBuffer.str(); } -string ProtoConverter::testCode(unsigned _invalidLength) +string ProtoConverter::testCallDataFunction(unsigned _invalidLength) { return Whiskers(R"( - uint returnVal = this.coder_public(); + uint returnVal = this.coder_calldata_public(); if (returnVal != 0) return returnVal; - returnVal = this.coder_external(); + returnVal = this.coder_calldata_external(); if (returnVal != 0) return uint(200000) + returnVal; - bytes memory argumentEncoding = abi.encode(); + bytes memory argumentEncoding = abi.encode(); returnVal = checkEncodedCall( - this.coder_public.selector, + this.coder_calldata_public.selector, argumentEncoding, , @@ -360,7 +449,7 @@ string ProtoConverter::testCode(unsigned _invalidLength) return returnVal; returnVal = checkEncodedCall( - this.coder_external.selector, + this.coder_calldata_external.selector, argumentEncoding, , @@ -370,26 +459,32 @@ string ProtoConverter::testCode(unsigned _invalidLength) return 0; )") - ("parameterNames", dev::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter)) + ("argumentNames", m_argsCoder.str()) ("invalidLengthFuzz", std::to_string(_invalidLength)) ("isRightPadded", isLastDynParamRightPadded() ? "true" : "false") ("atLeastOneVar", m_varCounter > 0) .render(); } -string ProtoConverter::helperFunctions() +string ProtoConverter::testReturnDataFunction() { - stringstream helperFuncs; - helperFuncs << R"( - function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) { - if(a.length != b.length) - return false; - for (uint i = 0; i < a.length; i++) - if (a[i] != b[i]) - return false; - return true; - } + return Whiskers(R"( + + () = this.coder_returndata_external(); + + + return 0; + )") + ("varsPresent", !m_typedReturn.str().empty()) + ("varDecl", m_typedReturn.str()) + ("equality_checks", m_checks.str()) + .render(); +} +string ProtoConverter::calldataHelperFunctions() +{ + stringstream calldataHelperFuncs; + calldataHelperFuncs << R"( /// Accepts function selector, correct argument encoding, and length of /// invalid encoding and returns the correct and incorrect abi encoding /// for calling the function specified by the function selector. @@ -452,19 +547,18 @@ string ProtoConverter::helperFunctions() if (success == true) return 400001; return 0; - } - )"; + })"; // These are callee functions that encode from storage, decode to // memory/calldata and check if decoded value matches storage value // return true on successful match, false otherwise - helperFuncs << Whiskers(R"( - function coder_public() public pure returns (uint) { + calldataHelperFuncs << Whiskers(R"( + function coder_calldata_public() public pure returns (uint) { return 0; } - function coder_external() external pure returns (uint) { + function coder_calldata_external() external pure returns (uint) { return 0; } @@ -473,6 +567,25 @@ string ProtoConverter::helperFunctions() ("equality_checks", equalityChecksAsString()) ("parameters_calldata", typedParametersAsString(CalleeType::EXTERNAL)) .render(); + + return calldataHelperFuncs.str(); +} + +string ProtoConverter::commonHelperFunctions() +{ + stringstream helperFuncs; + helperFuncs << R"( + /// Compares bytes, returning true if they are equal and false otherwise. + function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) { + if(a.length != b.length) + return false; + for (uint i = 0; i < a.length; i++) + if (a[i] != b[i]) + return false; + return true; + } + )"; + return helperFuncs.str(); } @@ -481,6 +594,9 @@ void ProtoConverter::visit(Contract const& _x) string pragmas = R"(pragma solidity >=0.0; pragma experimental ABIEncoderV2;)"; + // Record test spec + m_test = _x.test(); + // TODO: Support more than one but less than N state variables auto storageBuffers = visit(_x.state_vars()); string storageVarDecls = storageBuffers.first; @@ -499,7 +615,7 @@ pragma experimental ABIEncoderV2;)"; ostringstream contractBody; contractBody << storageVarDecls << testFunction - << helperFunctions(); + << commonHelperFunctions(); m_output << Whiskers(R"( @@ -1085,4 +1201,4 @@ string ValueGetterVisitor::bytesArrayValueAsString(unsigned _counter, bool _isHe _counter, _isHexLiteral ); -} \ No newline at end of file +} diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h index bd20b1628..236554bbc 100644 --- a/test/tools/ossfuzz/protoToAbiV2.h +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -19,84 +19,118 @@ /** * Template of the solidity test program generated by this converter is as follows: * - * pragma solidity >=0.0; - * pragma experimental ABIEncoderV2; + * pragma solidity >=0.0; + * pragma experimental ABIEncoderV2; * - * contract C { - * // State variable - * string x_0; - * // Test function that is called by the VM. - * function test() public returns (uint) { - * // Local variable - * bytes x_1 = "1"; - * x_0 = "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"; - * uint returnVal = this.coder_public(x_0, x_1); - * if (returnVal != 0) - * return returnVal; - * // Since the return codes in the public and external coder functions are identical - * // we offset error code by a fixed amount (200000) for differentiation. - * returnVal = this.coder_external(x_0, x_1); - * if (returnVal != 0) - * return 200000 + returnVal; - * // Encode parameters - * bytes memory argumentEncoding = abi.encode(); - * returnVal = checkEncodedCall(this.coder_public.selector, argumentEncoding, ); - * // Check if calls to coder_public meet expectations for correctly/incorrectly encoded data. - * if (returnVal != 0) - * return returnVal; + * contract C { + * // State variable + * string sv_0; + * // Test function that is called by the VM. + * // There are 2 variations of this function: one returns + * // the output of test_calldata_coding() and the other + * // returns the output of test_returndata_coding(). The + * // proto field called Test decides which one of the two + * // are chosen for creating a test case. + * function test() public returns (uint) { + * // The protobuf field "Contract.test" decides which of + * // the two internal functions "test_calldata_coding()" + * // and "test_returndata_coding()" are called. Here, + * // we assume that the protobuf field equals "CALLDATA_CODER" + * return this.test_calldata_coding() + * } * - * returnVal = checkEncodedCall(this.coder_external.selector, argumentEncoding, ); - * // Check if calls to coder_external meet expectations for correctly/incorrectly encoded data. - * // Offset return value to distinguish between failures originating from coder_public and coder_external. - * if (returnVal != 0) - * return uint(200000) + returnVal; - * // Return zero if all checks pass. - * return 0; - * } + * // The following function is generated if the protobuf field + * // "Contract.test" is equal to "RETURNDATA_CODER". + * function test_returndata_coding() internal returns (uint) { + * string memory lv_0, bytes memory lv_1 = test_returndata_external(); + * if (lv_0 != 044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d) + * return 1; + * if (lv_1 != "1") + * return 2; + * return 0; + * } * - * /// Accepts function selector, correct argument encoding, and an invalid encoding length as input. - * /// Returns a non-zero value if either call with correct encoding fails or call with incorrect encoding - * /// succeeds. Returns zero if both calls meet expectation. - * function checkEncodedCall(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz) - * public returns (uint) { - * ... - * } + * // The following function is generated if the protobuf field + * // "Contract.test" is equal to "RETURNDATA_CODER". + * function test_returndata_external() external returns (string memory, bytes memory) + * { + * sv_0 = "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"; + * bytes memory lv_0 = "1"; + * return (sv_0, lv_0); + * } * - * /// Accepts function selector, correct argument encoding, and length of invalid encoding and returns - * /// the correct and incorrect abi encoding for calling the function specified by the function selector. - * function createEncoding(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz) - * internal pure returns (bytes memory, bytes memory) { - * ... - * } + * // The following function is generated if the protobuf field + * // "Contract.test" is equal to "CALLDATA_CODER". + * function test_calldata_coding() internal returns (uint) { + * // Local variable + * bytes lv_1 = "1"; + * sv_0 = "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"; + * uint returnVal = this.coder_public(sv_0, lv_1); + * if (returnVal != 0) + * return returnVal; + * // Since the return codes in the public and external coder functions are identical + * // we offset error code by a fixed amount (200000) for differentiation. + * returnVal = this.coder_external(sv_0, lv_1); + * if (returnVal != 0) + * return 200000 + returnVal; + * // Encode parameters + * bytes memory argumentEncoding = abi.encode(); + * returnVal = checkEncodedCall(this.coder_public.selector, argumentEncoding, ); + * // Check if calls to coder_public meet expectations for correctly/incorrectly encoded data. + * if (returnVal != 0) + * return returnVal; * - * /// Compares two dynamically sized bytes arrays for equality. - * function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) { - * ... - * } + * returnVal = checkEncodedCall(this.coder_external.selector, argumentEncoding, ); + * // Check if calls to coder_external meet expectations for correctly/incorrectly encoded data. + * // Offset return value to distinguish between failures originating from coder_public and coder_external. + * if (returnVal != 0) + * return uint(200000) + returnVal; + * // Return zero if all checks pass. + * return 0; + * } * - * // Public function that is called by test() function. Accepts one or more arguments and returns - * // a uint value (zero if abi en/decoding was successful, non-zero otherwise) - * function coder_public(string memory c_0, bytes memory c_1) public pure returns (uint) { - * if (!bytesCompare(bytes(c_0), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d")) - * return 1; - * if (!bytesCompare(c_1, "1")) - * return 2; - * return 0; - * } + * /// Accepts function selector, correct argument encoding, and an invalid encoding length as input. + * /// Returns a non-zero value if either call with correct encoding fails or call with incorrect encoding + * /// succeeds. Returns zero if both calls meet expectation. + * function checkEncodedCall(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz) + * public returns (uint) { + * ... + * } * - * // External function that is called by test() function. Accepts one or more arguments and returns - * // a uint value (zero if abi en/decoding was successful, non-zero otherwise) - * function coder_external(string calldata c_0, bytes calldata c_1) external pure returns (uint) { - * if (!bytesCompare(bytes(c_0), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d")) - * return 1; - * if (!bytesCompare(c_1, "1")) - * return 2; - * return 0; - * } - * } + * /// Accepts function selector, correct argument encoding, and length of invalid encoding and returns + * /// the correct and incorrect abi encoding for calling the function specified by the function selector. + * function createEncoding(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz) + * internal pure returns (bytes memory, bytes memory) { + * ... + * } + * + * /// Compares two dynamically sized bytes arrays for equality. + * function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) { + * ... + * } + * + * // Public function that is called by test() function. Accepts one or more arguments and returns + * // a uint value (zero if abi en/decoding was successful, non-zero otherwise) + * function coder_public(string memory c_0, bytes memory c_1) public pure returns (uint) { + * if (!bytesCompare(bytes(c_0), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d")) + * return 1; + * if (!bytesCompare(c_1, "1")) + * return 2; + * return 0; + * } + * + * // External function that is called by test() function. Accepts one or more arguments and returns + * // a uint value (zero if abi en/decoding was successful, non-zero otherwise) + * function coder_external(string calldata c_0, bytes calldata c_1) external pure returns (uint) { + * if (!bytesCompare(bytes(c_0), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d")) + * return 1; + * if (!bytesCompare(c_1, "1")) + * return 2; + * return 0; + * } + * } */ - namespace dev { namespace test @@ -242,6 +276,20 @@ private: Delimiter _delimiter = Delimiter::ADD ); + /// Append types to typed stream used by returndata coders. + void appendTypes( + bool _isValueType, + std::string const& _typeString, + Delimiter _delimiter + ); + + /// Append typed return value. + void appendTypedReturn( + bool _isValueType, + std::string const& _typeString, + Delimiter _delimiter + ); + /// Returns a Solidity variable declaration statement /// @param _type: string containing Solidity type of the /// variable to be declared. @@ -262,11 +310,17 @@ private: /// Return comma separated typed function parameters as string std::string typedParametersAsString(CalleeType _calleeType); - /// Return Solidity helper functions as string - std::string helperFunctions(); + /// Return commonly used Solidity helper functions as string + std::string commonHelperFunctions(); - /// Return top-level test code as string - std::string testCode(unsigned _invalidLength); + /// Return helper functions used to test calldata coding + std::string calldataHelperFunctions(); + + /// Return top-level calldata coder test function as string + std::string testCallDataFunction(unsigned _invalidLength); + + /// Return top-level returndata coder test function as string + std::string testReturnDataFunction(); /// Return the next variable count that is used for /// variable naming. @@ -276,15 +330,30 @@ private: } /// Return a pair of names for Solidity variable and the same variable when - /// passed as a function parameter. - static std::pair newVarNames(unsigned _varCounter) + /// passed either as a function parameter or used to store the tuple + /// returned from a function. + /// @param _varCounter: name suffix + /// @param _stateVar: predicate that is true for state variables, false otherwise + std::pair newVarNames(unsigned _varCounter, bool _stateVar) { + std::string varName = _stateVar ? s_stateVarNamePrefix : s_localVarNamePrefix; return std::make_pair( - s_varNamePrefix + std::to_string(_varCounter), - s_paramNamePrefix + std::to_string(_varCounter) + varName + std::to_string(_varCounter), + paramName() + std::to_string(_varCounter) ); } + std::string paramName() + { + switch (m_test) + { + case Contract_Test::Contract_Test_CALLDATA_CODER: + return s_paramNamePrefix; + case Contract_Test::Contract_Test_RETURNDATA_CODER: + return s_localVarNamePrefix; + } + } + /// Checks if the last dynamically encoded Solidity type is right /// padded, returning true if it is and false otherwise. bool isLastDynParamRightPadded() @@ -303,6 +372,12 @@ private: /// Contains typed parameter list to be passed to callee functions std::ostringstream m_typedParamsExternal; std::ostringstream m_typedParamsPublic; + /// Contains type stream to be used in returndata coder function + /// signature + std::ostringstream m_types; + std::ostringstream m_typedReturn; + /// Argument names to be passed to coder functions + std::ostringstream m_argsCoder; /// Predicate that is true if we are in contract scope bool m_isStateVar; unsigned m_counter; @@ -316,9 +391,12 @@ private: /// Struct counter unsigned m_structCounter; unsigned m_numStructsAdded; + /// Enum stating abiv2 coder to be tested + Contract_Test m_test; /// Prefixes for declared and parameterized variable names - static auto constexpr s_varNamePrefix = "x_"; - static auto constexpr s_paramNamePrefix = "c_"; + static auto constexpr s_localVarNamePrefix = "lv_"; + static auto constexpr s_stateVarNamePrefix = "sv_"; + static auto constexpr s_paramNamePrefix = "p_"; }; /// Visitor interface for Solidity protobuf types. @@ -857,4 +935,4 @@ public: }; } } -} \ No newline at end of file +}