#include using namespace std; using namespace solidity; using namespace solidity::util; using namespace solidity::test::abiv2fuzzer; string ProtoConverter::getVarDecl( string const& _type, string const& _varName, string const& _qualifier ) { // One level of indentation for state variable declarations // Two levels of indentation for local variable declarations return Whiskers(R"( ;)" ) ("isLocalVar", !m_isStateVar) ("type", _type) ("qual", !_qualifier.empty()) ("qualifier", _qualifier) ("varName", _varName) .render() + "\n"; } pair ProtoConverter::visit(Type const& _type) { switch (_type.type_oneof_case()) { case Type::kVtype: return visit(_type.vtype()); case Type::kNvtype: return visit(_type.nvtype()); case Type::TYPE_ONEOF_NOT_SET: return make_pair("", ""); } } pair ProtoConverter::visit(ValueType const& _type) { switch (_type.value_type_oneof_case()) { case ValueType::kBoolty: return visit(_type.boolty()); case ValueType::kInty: return visit(_type.inty()); case ValueType::kByty: return visit(_type.byty()); case ValueType::kAdty: return visit(_type.adty()); case ValueType::VALUE_TYPE_ONEOF_NOT_SET: return make_pair("", ""); } } pair ProtoConverter::visit(NonValueType const& _type) { switch (_type.nonvalue_type_oneof_case()) { case NonValueType::kDynbytearray: return visit(_type.dynbytearray()); case NonValueType::kArrtype: if (ValidityVisitor().visit(_type.arrtype())) return visit(_type.arrtype()); else return make_pair("", ""); case NonValueType::kStype: if (ValidityVisitor().visit(_type.stype())) return visit(_type.stype()); else return make_pair("", ""); case NonValueType::NONVALUE_TYPE_ONEOF_NOT_SET: return make_pair("", ""); } } pair ProtoConverter::visit(BoolType const& _type) { return processType(_type, true); } pair ProtoConverter::visit(IntegerType const& _type) { return processType(_type, true); } pair ProtoConverter::visit(FixedByteType const& _type) { return processType(_type, true); } pair ProtoConverter::visit(AddressType const& _type) { return processType(_type, true); } pair ProtoConverter::visit(DynamicByteArrayType const& _type) { return processType(_type, false); } pair ProtoConverter::visit(ArrayType const& _type) { return processType(_type, false); } pair ProtoConverter::visit(StructType const& _type) { return processType(_type, false); } template pair ProtoConverter::processType(T const& _type, bool _isValueType) { ostringstream local, global; 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"; auto varDeclBuffers = varDecl( varName, paramName, _type, _isValueType, location ); global << varDeclBuffers.first; local << varDeclBuffers.second; auto assignCheckBuffers = assignChecker(varName, paramName, _type); global << assignCheckBuffers.first; local << assignCheckBuffers.second; m_structCounter += m_numStructsAdded; return make_pair(global.str(), local.str()); } template pair ProtoConverter::varDecl( string const& _varName, string const& _paramName, T _type, bool _isValueType, string const& _location ) { ostringstream local, global; TypeVisitor tVisitor(m_structCounter); string typeStr = tVisitor.visit(_type); if (typeStr.empty()) return make_pair("", ""); // Append struct defs global << tVisitor.structDef(); m_numStructsAdded = tVisitor.numStructs(); // variable declaration if (m_isStateVar) global << getVarDecl(typeStr, _varName, _location); else local << getVarDecl(typeStr, _varName, _location); // Add typed params for calling public and external functions with said type appendTypedParams( CalleeType::PUBLIC, _isValueType, typeStr, _paramName, ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) ); appendTypedParams( CalleeType::EXTERNAL, _isValueType, typeStr, _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()) m_isLastDynParamRightPadded = true; return make_pair(global.str(), local.str()); } template pair ProtoConverter::assignChecker( string const& _varName, string const& _paramName, T _type ) { ostringstream local; AssignCheckVisitor acVisitor( _varName, _paramName, m_returnValue, m_isStateVar, m_counter, m_structCounter ); pair assignCheckStrPair = acVisitor.visit(_type); m_returnValue += acVisitor.errorStmts(); m_counter += acVisitor.counted(); m_checks << assignCheckStrPair.second; // State variables cannot be assigned in contract-scope // Therefore, we buffer their assignments and // render them in function scope later. local << assignCheckStrPair.first; return make_pair("", local.str()); } pair ProtoConverter::visit(VarDecl const& _x) { return visit(_x.type()); } std::string ProtoConverter::equalityChecksAsString() { return m_checks.str(); } std::string ProtoConverter::delimiterToString(Delimiter _delimiter) { switch (_delimiter) { case Delimiter::ADD: return ", "; case Delimiter::SKIP: return ""; } } /* When a new variable is declared, we can invoke this function * to prepare the typed param list to be passed to callee functions. * We independently prepare this list for "public" and "external" * callee functions. */ void ProtoConverter::appendTypedParams( CalleeType _calleeType, bool _isValueType, std::string const& _typeString, std::string const& _varName, Delimiter _delimiter ) { switch (_calleeType) { case CalleeType::PUBLIC: appendTypedParamsPublic(_isValueType, _typeString, _varName, _delimiter); break; case CalleeType::EXTERNAL: appendTypedParamsExternal(_isValueType, _typeString, _varName, _delimiter); break; } } 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, std::string const& _typeString, std::string const& _varName, Delimiter _delimiter ) { std::string qualifiedTypeString = ( _isValueType ? _typeString : _typeString + " calldata" ); m_typedParamsExternal << Whiskers(R"( )") ("delimiter", delimiterToString(_delimiter)) ("type", qualifiedTypeString) ("varName", _varName) .render(); } // Adds the qualifier "memory" to non-value parameter of an external function. void ProtoConverter::appendTypedParamsPublic( bool _isValueType, std::string const& _typeString, std::string const& _varName, Delimiter _delimiter ) { std::string qualifiedTypeString = ( _isValueType ? _typeString : _typeString + " memory" ); m_typedParamsPublic << Whiskers(R"( )") ("delimiter", delimiterToString(_delimiter)) ("type", qualifiedTypeString) ("varName", _varName) .render(); } std::string ProtoConverter::typedParametersAsString(CalleeType _calleeType) { switch (_calleeType) { case CalleeType::PUBLIC: return m_typedParamsPublic.str(); case CalleeType::EXTERNAL: return m_typedParamsExternal.str(); } } string ProtoConverter::visit(TestFunction const& _x, string const& _storageVarDefs) { // TODO: Support more than one but less than N local variables auto localVarBuffers = visit(_x.local_vars()); string structTypeDecl = localVarBuffers.first; string localVarDefs = localVarBuffers.second; ostringstream testBuffer; 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) ("functionDeclCalldata", functionDeclCalldata) ("functionDeclReturndata", functionDeclReturndata) ("storageVarDefs", _storageVarDefs) ("localVarDefs", localVarDefs) ("calldataTestCode", testCallDataFunction(static_cast(_x.invalid_encoding_length()))) ("returndataTestCode", testReturnDataFunction()) ("return_types", m_types.str()) ("return_values", m_argsCoder.str()) .render(); return testBuffer.str(); } string ProtoConverter::testCallDataFunction(unsigned _invalidLength) { return Whiskers(R"( uint returnVal = this.coder_calldata_public(); if (returnVal != 0) return returnVal; returnVal = this.coder_calldata_external(); if (returnVal != 0) return uint(200000) + returnVal; bytes memory argumentEncoding = abi.encode(); returnVal = checkEncodedCall( this.coder_calldata_public.selector, argumentEncoding, , ); if (returnVal != 0) return returnVal; returnVal = checkEncodedCall( this.coder_calldata_external.selector, argumentEncoding, , ); if (returnVal != 0) return uint(200000) + returnVal; return 0; )") ("argumentNames", m_argsCoder.str()) ("invalidLengthFuzz", std::to_string(_invalidLength)) ("isRightPadded", isLastDynParamRightPadded() ? "true" : "false") ("atLeastOneVar", m_varCounter > 0) .render(); } string ProtoConverter::testReturnDataFunction() { 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. function createEncoding( bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz, bool isRightPadded ) internal pure returns (bytes memory, bytes memory) { bytes memory validEncoding = new bytes(4 + argumentEncoding.length); // Ensure that invalidEncoding crops at least 32 bytes (padding length // is at most 31 bytes) if `isRightPadded` is true. // This is because shorter bytes/string values (whose encoding is right // padded) can lead to successful decoding when fewer than 32 bytes have // been cropped in the worst case. In other words, if `isRightPadded` is // true, then // 0 <= invalidLength <= argumentEncoding.length - 32 // otherwise // 0 <= invalidLength <= argumentEncoding.length - 1 uint invalidLength; if (isRightPadded) invalidLength = invalidLengthFuzz % (argumentEncoding.length - 31); else invalidLength = invalidLengthFuzz % argumentEncoding.length; bytes memory invalidEncoding = new bytes(4 + invalidLength); for (uint i = 0; i < 4; i++) validEncoding[i] = invalidEncoding[i] = funcSelector[i]; for (uint i = 0; i < argumentEncoding.length; i++) validEncoding[i+4] = argumentEncoding[i]; for (uint i = 0; i < invalidLength; i++) invalidEncoding[i+4] = argumentEncoding[i]; return (validEncoding, invalidEncoding); } /// 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, bool isRightPadded ) public returns (uint) { (bytes memory validEncoding, bytes memory invalidEncoding) = createEncoding( funcSelector, argumentEncoding, invalidLengthFuzz, isRightPadded ); (bool success, bytes memory returnVal) = address(this).call(validEncoding); uint returnCode = abi.decode(returnVal, (uint)); // Return non-zero value if call fails for correct encoding if (success == false || returnCode != 0) return 400000; (success, ) = address(this).call(invalidEncoding); // Return non-zero value if call succeeds for incorrect encoding 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 calldataHelperFuncs << Whiskers(R"( function coder_calldata_public() public pure returns (uint) { return 0; } function coder_calldata_external() external pure returns (uint) { return 0; } )") ("parameters_memory", typedParametersAsString(CalleeType::PUBLIC)) ("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(); } 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; string storageVarDefs = storageBuffers.second; m_isStateVar = false; string testFunction = visit(_x.testfunction(), storageVarDefs); /* Structure of contract body * - Storage variable declarations * - Struct definitions * - Test function * - Storage variable assignments * - Local variable definitions and assignments * - Test code proper (calls public and external functions) * - Helper functions */ ostringstream contractBody; contractBody << storageVarDecls << testFunction << commonHelperFunctions(); m_output << Whiskers(R"( )") ("pragmas", pragmas) ("contractStart", "contract C {") ("contractBody", contractBody.str()) ("contractEnd", "}") .render(); } string ProtoConverter::contractToString(Contract const& _input) { visit(_input); return m_output.str(); } /// Type visitor string TypeVisitor::visit(BoolType const&) { m_baseType = "bool"; return m_baseType; } string TypeVisitor::visit(IntegerType const& _type) { m_baseType = getIntTypeAsString(_type); return m_baseType; } string TypeVisitor::visit(FixedByteType const& _type) { m_baseType = getFixedByteTypeAsString(_type); return m_baseType; } string TypeVisitor::visit(AddressType const& _type) { m_baseType = getAddressTypeAsString(_type); return m_baseType; } string TypeVisitor::visit(ArrayType const& _type) { if (!ValidityVisitor().visit(_type)) return ""; string baseType = visit(_type.t()); solAssert(!baseType.empty(), ""); string arrayBraces = _type.is_static() ? string("[") + to_string(getStaticArrayLengthFromFuzz(_type.length())) + string("]") : string("[]"); m_baseType += arrayBraces; // If we don't know yet if the array will be dynamically encoded, // check again. If we already know that it will be, there's no // need to do anything. if (!m_isLastDynParamRightPadded) m_isLastDynParamRightPadded = DynParamVisitor().visit(_type); return baseType + arrayBraces; } string TypeVisitor::visit(DynamicByteArrayType const& _type) { m_isLastDynParamRightPadded = true; m_baseType = bytesArrayTypeAsString(_type); return m_baseType; } void TypeVisitor::structDefinition(StructType const& _type) { // Return an empty string if struct is empty solAssert(ValidityVisitor().visit(_type), ""); // Reset field counter and indentation unsigned wasFieldCounter = m_structFieldCounter; unsigned wasIndentation = m_indentation; m_indentation = 1; m_structFieldCounter = 0; // Commence struct declaration string structDef = lineString( "struct " + string(s_structNamePrefix) + to_string(m_structCounter) + " {" ); // Increase indentation for struct fields m_indentation++; for (auto const& t: _type.t()) { string type{}; if (!ValidityVisitor().visit(t)) continue; TypeVisitor tVisitor(m_structCounter + 1); type = tVisitor.visit(t); m_structCounter += tVisitor.numStructs(); m_structDef << tVisitor.structDef(); solAssert(!type.empty(), ""); structDef += lineString( Whiskers(R"( ;)") ("type", type) ("member", "m" + to_string(m_structFieldCounter++)) .render() ); } m_indentation--; structDef += lineString("}"); m_structCounter++; m_structDef << structDef; m_indentation = wasIndentation; m_structFieldCounter = wasFieldCounter; } string TypeVisitor::visit(StructType const& _type) { if (ValidityVisitor().visit(_type)) { // Add struct definition structDefinition(_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); } else m_baseType = {}; return m_baseType; } /// AssignCheckVisitor implementation pair AssignCheckVisitor::visit(BoolType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); } pair AssignCheckVisitor::visit(IntegerType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); } pair AssignCheckVisitor::visit(FixedByteType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); } pair AssignCheckVisitor::visit(AddressType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); return assignAndCheckStringPair(m_varName, m_paramName, value, value, DataType::VALUE); } pair AssignCheckVisitor::visit(DynamicByteArrayType const& _type) { string value = ValueGetterVisitor(counter()).visit(_type); DataType dataType = _type.type() == DynamicByteArrayType::BYTES ? DataType::BYTES : DataType::STRING; return assignAndCheckStringPair(m_varName, m_paramName, value, value, dataType); } pair AssignCheckVisitor::visit(ArrayType const& _type) { if (!ValidityVisitor().visit(_type)) return make_pair("", ""); // Obtain type of array to be resized and initialized string typeStr{}; unsigned wasStructCounter = m_structCounter; TypeVisitor tVisitor(m_structCounter); typeStr = tVisitor.visit(_type); pair resizeBuffer; string lengthStr; unsigned length; // Resize dynamic arrays if (!_type.is_static()) { length = getDynArrayLengthFromFuzz(_type.length(), counter()); lengthStr = to_string(length); if (m_stateVar) { // Dynamic storage arrays are resized via the empty push() operation resizeBuffer.first = Whiskers(R"(for (uint i = 0; i < ; i++) .push();)") ("indentation", indentation()) ("length", lengthStr) ("arrayRef", m_varName) .render() + "\n"; // Add a dynamic check on the resized length resizeBuffer.second = checkString(m_paramName + ".length", lengthStr, DataType::VALUE); } else { // Resizing memory arrays via the new operator string resizeOp = Whiskers(R"(new ())") ("fullTypeStr", typeStr) ("length", lengthStr) .render(); resizeBuffer = assignAndCheckStringPair( m_varName, m_paramName + ".length", resizeOp, lengthStr, DataType::VALUE ); } } else { length = getStaticArrayLengthFromFuzz(_type.length()); lengthStr = to_string(length); // Add check on length resizeBuffer.second = checkString(m_paramName + ".length", lengthStr, DataType::VALUE); } // Add assignCheckBuffer and check statements pair assignCheckBuffer; string wasVarName = m_varName; string wasParamName = m_paramName; for (unsigned i = 0; i < length; i++) { m_varName = wasVarName + "[" + to_string(i) + "]"; m_paramName = wasParamName + "[" + to_string(i) + "]"; pair assign = visit(_type.t()); assignCheckBuffer.first += assign.first; assignCheckBuffer.second += assign.second; if (i < length - 1) m_structCounter = wasStructCounter; } // Since struct visitor won't be called for zero-length // arrays, struct counter will not get incremented. Therefore, // we need to manually force a recursive struct visit. if (length == 0 && TypeVisitor().arrayOfStruct(_type)) visit(_type.t()); m_varName = wasVarName; m_paramName = wasParamName; // Compose resize and initialization assignment and check return make_pair( resizeBuffer.first + assignCheckBuffer.first, resizeBuffer.second + assignCheckBuffer.second ); } pair AssignCheckVisitor::visit(StructType const& _type) { if (!ValidityVisitor().visit(_type)) return make_pair("", ""); pair assignCheckBuffer; unsigned i = 0; // Increment struct counter m_structCounter++; string wasVarName = m_varName; string wasParamName = m_paramName; for (auto const& t: _type.t()) { m_varName = wasVarName + ".m" + to_string(i); m_paramName = wasParamName + ".m" + to_string(i); pair assign = visit(t); // If type is not well formed continue without // updating state. if (assign.first.empty() && assign.second.empty()) continue; assignCheckBuffer.first += assign.first; assignCheckBuffer.second += assign.second; i++; } m_varName = wasVarName; m_paramName = wasParamName; return assignCheckBuffer; } pair AssignCheckVisitor::assignAndCheckStringPair( string const& _varRef, string const& _checkRef, string const& _assignValue, string const& _checkValue, DataType _type ) { return make_pair(assignString(_varRef, _assignValue), checkString(_checkRef, _checkValue, _type)); } string AssignCheckVisitor::assignString(string const& _ref, string const& _value) { string assignStmt = Whiskers(R"( = ;)") ("ref", _ref) ("value", _value) .render(); return indentation() + assignStmt + "\n"; } string AssignCheckVisitor::checkString(string const& _ref, string const& _value, DataType _type) { string checkPred; switch (_type) { case DataType::STRING: checkPred = Whiskers(R"(!bytesCompare(bytes(), ))") ("varName", _ref) ("value", _value) .render(); break; case DataType::BYTES: checkPred = Whiskers(R"(!bytesCompare(, ))") ("varName", _ref) ("value", _value) .render(); break; case DataType::VALUE: checkPred = Whiskers(R"( != )") ("varName", _ref) ("value", _value) .render(); break; case DataType::ARRAY: solUnimplemented("Proto ABIv2 fuzzer: Invalid data type."); } string checkStmt = Whiskers(R"(if () return ;)") ("checkPred", checkPred) ("errCode", to_string(m_errorCode++)) .render(); return indentation() + checkStmt + "\n"; } /// ValueGetterVisitor string ValueGetterVisitor::visit(BoolType const&) { return counter() % 2 ? "true" : "false"; } string ValueGetterVisitor::visit(IntegerType const& _type) { return integerValueAsString( _type.is_signed(), getIntWidth(_type), counter() ); } string ValueGetterVisitor::visit(FixedByteType const& _type) { return fixedByteValueAsString( getFixedByteWidth(_type), counter() ); } string ValueGetterVisitor::visit(AddressType const&) { return addressValueAsString(counter()); } string ValueGetterVisitor::visit(DynamicByteArrayType const& _type) { return bytesArrayValueAsString( counter(), getDataTypeOfDynBytesType(_type) == DataType::BYTES ); } std::string ValueGetterVisitor::integerValueAsString(bool _sign, unsigned _width, unsigned _counter) { if (_sign) return intValueAsString(_width, _counter); else return uintValueAsString(_width, _counter); } /* Input(s) * - Unsigned integer to be hashed * - Width of desired uint value * Processing * - Take hash of first parameter and mask it with the max unsigned value for given bit width * Output * - string representation of uint value */ std::string ValueGetterVisitor::uintValueAsString(unsigned _width, unsigned _counter) { solAssert( (_width % 8 == 0), "Proto ABIv2 Fuzzer: Unsigned integer width is not a multiple of 8" ); return maskUnsignedIntToHex(_counter, _width/4); } /* Input(s) * - counter to be hashed to derive a value for Integer type * - Width of desired int value * Processing * - Take hash of first parameter and mask it with the max signed value for given bit width * Output * - string representation of int value */ std::string ValueGetterVisitor::intValueAsString(unsigned _width, unsigned _counter) { solAssert( (_width % 8 == 0), "Proto ABIv2 Fuzzer: Signed integer width is not a multiple of 8" ); return maskUnsignedIntToHex(_counter, ((_width/4) - 1)); } std::string ValueGetterVisitor::croppedString( unsigned _numBytes, unsigned _counter, bool _isHexLiteral ) { solAssert( _numBytes > 0 && _numBytes <= 32, "Proto ABIv2 fuzzer: Too short or too long a cropped string" ); // Number of masked nibbles is twice the number of bytes for a // hex literal of _numBytes bytes. For a string literal, each nibble // is treated as a character. unsigned numMaskNibbles = _isHexLiteral ? _numBytes * 2 : _numBytes; // Start position of substring equals totalHexStringLength - numMaskNibbles // totalHexStringLength = 64 + 2 = 66 // e.g., 0x12345678901234567890123456789012 is a total of 66 characters // |---------------------^-----------| // <--- start position---><--numMask-> // <-----------total length ---------> // Note: This assumes that maskUnsignedIntToHex() invokes toHex(..., HexPrefix::Add) unsigned startPos = 66 - numMaskNibbles; // Extracts the least significant numMaskNibbles from the result // of maskUnsignedIntToHex(). return maskUnsignedIntToHex( _counter, numMaskNibbles ).substr(startPos, numMaskNibbles); } std::string ValueGetterVisitor::hexValueAsString( unsigned _numBytes, unsigned _counter, bool _isHexLiteral, bool _decorate ) { solAssert(_numBytes > 0 && _numBytes <= 32, "Proto ABIv2 fuzzer: Invalid hex length" ); // If _decorate is set, then we return a hex"" or a "" string. if (_numBytes == 0) return Whiskers(R"(hex"")") ("decorate", _decorate) ("isHex", _isHexLiteral) .render(); // This is needed because solidity interprets a 20-byte 0x prefixed hex literal as an address // payable type. return Whiskers(R"(hex"")") ("decorate", _decorate) ("isHex", _isHexLiteral) ("value", croppedString(_numBytes, _counter, _isHexLiteral)) .render(); } std::string ValueGetterVisitor::fixedByteValueAsString(unsigned _width, unsigned _counter) { solAssert( (_width >= 1 && _width <= 32), "Proto ABIv2 Fuzzer: Fixed byte width is not between 1--32" ); return hexValueAsString(_width, _counter, /*isHexLiteral=*/true); } std::string ValueGetterVisitor::addressValueAsString(unsigned _counter) { return Whiskers(R"(address())") ("value", uintValueAsString(160, _counter)) .render(); } std::string ValueGetterVisitor::variableLengthValueAsString( unsigned _numBytes, unsigned _counter, bool _isHexLiteral ) { // TODO: Move this to caller // solAssert(_numBytes >= 0 && _numBytes <= s_maxDynArrayLength, // "Proto ABIv2 fuzzer: Invalid hex length" // ); if (_numBytes == 0) return Whiskers(R"(hex"")") ("isHex", _isHexLiteral) .render(); unsigned numBytesRemaining = _numBytes; // Stores the literal string output{}; // If requested value is shorter than or exactly 32 bytes, // the literal is the return value of hexValueAsString. if (numBytesRemaining <= 32) output = hexValueAsString( numBytesRemaining, _counter, _isHexLiteral, /*decorate=*/false ); // If requested value is longer than 32 bytes, the literal // is obtained by duplicating the return value of hexValueAsString // until we reach a value of the requested size. else { // Create a 32-byte value to be duplicated and // update number of bytes to be appended. // Stores the cached literal that saves us // (expensive) calls to keccak256. string cachedString = hexValueAsString( /*numBytes=*/32, _counter, _isHexLiteral, /*decorate=*/false ); output = cachedString; numBytesRemaining -= 32; // Append bytes from cachedString until // we create a value of desired length. unsigned numAppendedBytes; while (numBytesRemaining > 0) { // We append at most 32 bytes at a time numAppendedBytes = numBytesRemaining >= 32 ? 32 : numBytesRemaining; output += cachedString.substr( 0, // Double the substring length for hex literals since each // character is actually half a byte (or a nibble). _isHexLiteral ? numAppendedBytes * 2 : numAppendedBytes ); numBytesRemaining -= numAppendedBytes; } solAssert( numBytesRemaining == 0, "Proto ABIv2 fuzzer: Logic flaw in variable literal creation" ); } if (_isHexLiteral) solAssert( output.size() == 2 * _numBytes, "Proto ABIv2 fuzzer: Generated hex literal is of incorrect length" ); else solAssert( output.size() == _numBytes, "Proto ABIv2 fuzzer: Generated string literal is of incorrect length" ); // Decorate output return Whiskers(R"(hex"")") ("isHexLiteral", _isHexLiteral) ("value", output) .render(); } string ValueGetterVisitor::bytesArrayValueAsString(unsigned _counter, bool _isHexLiteral) { return variableLengthValueAsString( getVarLength(_counter), _counter, _isHexLiteral ); }