diff --git a/test/tools/ossfuzz/abiV2FuzzerCommon.cpp b/test/tools/ossfuzz/abiV2FuzzerCommon.cpp new file mode 100644 index 000000000..8d691c288 --- /dev/null +++ b/test/tools/ossfuzz/abiV2FuzzerCommon.cpp @@ -0,0 +1,36 @@ +#include + +using namespace dev::test::abiv2fuzzer; + +SolidityCompilationFramework::SolidityCompilationFramework(langutil::EVMVersion _evmVersion) +{ + m_evmVersion = _evmVersion; +} + +dev::bytes SolidityCompilationFramework::compileContract( + std::string const& _sourceCode, + std::string const& _contractName +) +{ + std::string sourceCode = _sourceCode; + m_compiler.setSources({{"", sourceCode}}); + m_compiler.setEVMVersion(m_evmVersion); + m_compiler.setOptimiserSettings(m_optimiserSettings); + if (!m_compiler.compile()) + { + langutil::SourceReferenceFormatter formatter(std::cerr); + + for (auto const& error: m_compiler.errors()) + formatter.printExceptionInformation( + *error, + formatter.formatErrorInformation(*error) + ); + std::cerr << "Compiling contract failed" << std::endl; + } + dev::eth::LinkerObject obj = m_compiler.runtimeObject( + _contractName.empty() ? + m_compiler.lastContractName() : + _contractName + ); + return obj.bytecode; +} diff --git a/test/tools/ossfuzz/abiV2FuzzerCommon.h b/test/tools/ossfuzz/abiV2FuzzerCommon.h new file mode 100644 index 000000000..7a49cb8e5 --- /dev/null +++ b/test/tools/ossfuzz/abiV2FuzzerCommon.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace dev +{ +namespace test +{ +namespace abiv2fuzzer +{ +class SolidityCompilationFramework +{ +public: + explicit SolidityCompilationFramework(langutil::EVMVersion _evmVersion = {}); + + Json::Value getMethodIdentifiers() + { + return m_compiler.methodIdentifiers(m_compiler.lastContractName()); + } + dev::bytes compileContract( + std::string const& _sourceCode, + std::string const& _contractName = {} + ); +protected: + dev::solidity::CompilerStack m_compiler; + langutil::EVMVersion m_evmVersion; + dev::solidity::OptimiserSettings m_optimiserSettings = dev::solidity::OptimiserSettings::full(); +}; +} +} +} diff --git a/test/tools/ossfuzz/abiV2Proto.proto b/test/tools/ossfuzz/abiV2Proto.proto new file mode 100644 index 000000000..87665a02b --- /dev/null +++ b/test/tools/ossfuzz/abiV2Proto.proto @@ -0,0 +1,106 @@ +/* + 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 . +*/ + +syntax = "proto2"; + +// Flattened specification of array dimension +// If is_static is false, and this array dimension is contained +// inside another dimension e.g., x[][2] ([2] being the outer dimension) +// then `length` for this dimension is the length of the first dynamically +// sized array. The other (n-1) lengths are unspecified +message ArrayDimensionInfo { + required uint32 length = 1; + required bool is_static = 2; +} + +// TODO: Add more base types +// See https://github.com/ethereum/solidity/issues/6749 +message ArrayType { + oneof base_type_oneof { + IntegerType inty = 1; + FixedByteType byty = 2; + AddressType adty = 3; + StructType stty = 4; + } + repeated ArrayDimensionInfo info = 5; +} + +// uint8...256, int8...256 +message IntegerType { + required bool is_signed = 1; + required uint32 width = 2; +} + +// bytes1, bytes2,..., bytes32 +message FixedByteType { + required uint32 width = 1; +} + +// address, address payable +message AddressType { + required bool payable = 1; +} + +message ValueType { + oneof value_type_oneof { + IntegerType inty = 1; + FixedByteType byty = 2; + AddressType adty = 3; + } +} + +// bytes/string +message DynamicByteArrayType { + enum DType { + BYTES = 0; + STRING = 1; + } + required DType type = 1; +} + +message NonValueType { + oneof nonvalue_type_oneof { + DynamicByteArrayType dynbytearray = 1; + ArrayType arrtype = 2; + StructType stype = 3; + } +} + +message Type { + oneof type_oneof { + ValueType vtype = 1; + NonValueType nvtype = 2; + } +} + +// TODO: This must not reference itself either directly or indirectly +message StructType {} + +message VarDecl { + required Type type = 1; +} + +message TestFunction { + required VarDecl local_vars = 1; +} + +message Contract { + required VarDecl state_vars = 1; + required TestFunction testfunction = 2; +} + +package dev.test.abiv2fuzzer; \ No newline at end of file diff --git a/test/tools/ossfuzz/abiV2ProtoFuzzer.cpp b/test/tools/ossfuzz/abiV2ProtoFuzzer.cpp new file mode 100644 index 000000000..2146d5ba5 --- /dev/null +++ b/test/tools/ossfuzz/abiV2ProtoFuzzer.cpp @@ -0,0 +1,60 @@ +/* + 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 dev::test::abiv2fuzzer; +using namespace dev; +using namespace std; + +DEFINE_PROTO_FUZZER(Contract const& _input) +{ + string contract_source = ProtoConverter{}.contractToString(_input); + + 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 << contract_source; + } + + // Raw runtime byte code generated by solidity + dev::bytes byteCode; + std::string hexEncodedInput; + + try + { + // Compile contract generated by the proto fuzzer + SolidityCompilationFramework solCompilationFramework; + std::string contractName = ":Factory"; + byteCode = solCompilationFramework.compileContract(contract_source, contractName); + Json::Value methodIdentifiers = solCompilationFramework.getMethodIdentifiers(); + // We always call the function test() that is defined in proto converter template + hexEncodedInput = methodIdentifiers["test()"].asString(); + } + catch (...) + { + cout << contract_source << endl; + throw; + } + // TODO: Call evmone wrapper here + return; +} \ No newline at end of file diff --git a/test/tools/ossfuzz/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp new file mode 100644 index 000000000..01bbaf545 --- /dev/null +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -0,0 +1,718 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev::solidity; +using namespace dev::test::abiv2fuzzer; + +// Create a new variable declaration and append said variable to function parameter lists +// of coder functions. +// Declared name is x_; parameterized name is c_ +// where is a monotonically increasing integer. +void ProtoConverter::createDeclAndParamList( + std::string const& _type, + DataType _dataType, + std::string& _varName, + std::string& _paramName +) +{ + auto varNames = newVarNames(getNextVarCounter()); + _varName = varNames.first; + _paramName = varNames.second; + + // Declare array + appendVarDeclToOutput(_type, _varName, getQualifier(_dataType)); + + // Add typed params for calling public and external functions with said type + appendTypedParams( + CalleeType::PUBLIC, + isValueType(_dataType), + _type, + _paramName, + ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) + ); + appendTypedParams( + CalleeType::EXTERNAL, + isValueType(_dataType), + _type, + _paramName, + ((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) + ); +} + +void ProtoConverter::visitArrayType(std::string const& _baseType, ArrayType const& _x) +{ + std::string type = arrayTypeAsString(_baseType, _x); + std::string varName, paramName; + createDeclAndParamList(type, DataType::ARRAY, varName, paramName); + // Resize-initialize array and add checks + resizeInitArray(_x, _baseType, varName, paramName); +} + +void ProtoConverter::visitType( + DataType _dataType, + std::string const& _type, + std::string const& _value +) +{ + std::string varName, paramName; + createDeclAndParamList(_type, _dataType, varName, paramName); + addCheckedVarDef(_dataType, varName, paramName, _value); +} + +void ProtoConverter::appendVarDeclToOutput( + std::string const& _type, + std::string const& _varName, + std::string const& _qualifier +) +{ + // One level of indentation for state variable declarations + // Two levels of indentation for local variable declarations + m_output << Whiskers(R"( + ;)" + ) + ("isLocalVar", !m_isStateVar) + ("type", _type) + ("qual", !_qualifier.empty()) + ("qualifier", _qualifier) + ("varName", _varName) + .render(); +} + +void ProtoConverter::appendChecks( + DataType _type, + std::string const& _varName, + std::string const& _rhs +) +{ + std::string check = {}; + switch (_type) + { + case DataType::STRING: + check = Whiskers(R"(!bytesCompare(bytes(), ))") + ("varName", _varName) + ("value", _rhs) + .render(); + break; + case DataType::BYTES: + check = Whiskers(R"(!bytesCompare(, ))") + ("varName", _varName) + ("value", _rhs) + .render(); + break; + case DataType::VALUE: + check = Whiskers(R"( != )") + ("varName", _varName) + ("value", _rhs) + .render(); + break; + case DataType::ARRAY: + solUnimplemented("Proto ABIv2 fuzzer: Invalid data type."); + } + + // Each (failing) check returns a unique value to simplify debugging. + m_checks << Whiskers(R"( + if () return ;)" + ) + ("check", check) + ("returnVal", std::to_string(m_returnValue++)) + .render(); +} + +void ProtoConverter::addVarDef(std::string const& _varName, std::string const& _rhs) +{ + std::string varDefString = Whiskers(R"( + = ;)" + ) + ("varName", _varName) + ("rhs", _rhs) + .render(); + + // State variables cannot be assigned in contract-scope + // Therefore, we buffer their assignments and + // render them in function scope later. + if (m_isStateVar) + m_statebuffer << varDefString; + else + m_output << varDefString; +} + +void ProtoConverter::addCheckedVarDef( + DataType _type, + std::string const& _varName, + std::string const& _paramName, + std::string const& _rhs) +{ + addVarDef(_varName, _rhs); + appendChecks(_type, _paramName, _rhs); +} + +// Runtime check for array length. +void ProtoConverter::checkResizeOp(std::string const& _paramName, unsigned _len) +{ + appendChecks(DataType::VALUE, _paramName + ".length", std::to_string(_len)); +} + +/* 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 ProtoConverter::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 ProtoConverter::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 ProtoConverter::addressValueAsString(unsigned _counter) +{ + return Whiskers(R"(address())") + ("value", uintValueAsString(160, _counter)) + .render(); +} + +std::string ProtoConverter::fixedByteValueAsString(unsigned _width, unsigned _counter) +{ + solAssert( + (_width >= 1 && _width <= 32), + "Proto ABIv2 Fuzzer: Fixed byte width is not between 1--32" + ); + // Masked value must contain twice the number of nibble "f"'s as _width + unsigned numMaskNibbles = _width * 2; + // 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", + // and replaces "0x" with "hex\"...\"" string. + // This is needed because solidity interprets a 20-byte 0x prefixed hex literal as an address + // payable type. + return Whiskers(R"(hex"")") + ("value", maskUnsignedIntToHex(_counter, numMaskNibbles).substr(startPos, numMaskNibbles)) + .render(); +} + +std::string ProtoConverter::integerValueAsString(bool _sign, unsigned _width, unsigned _counter) +{ + if (_sign) + return intValueAsString(_width, _counter); + else + return uintValueAsString(_width, _counter); +} + +std::string ProtoConverter::bytesArrayTypeAsString(DynamicByteArrayType const& _x) +{ + switch (_x.type()) + { + case DynamicByteArrayType::BYTES: + return "bytes"; + case DynamicByteArrayType::STRING: + return "string"; + } +} + +std::string ProtoConverter::structTypeAsString(StructType const&) +{ + // TODO: Implement this + return {}; +} + +void ProtoConverter::visit(IntegerType const& _x) +{ + visitType( + DataType::VALUE, + getIntTypeAsString(_x), + integerValueAsString(isIntSigned(_x), getIntWidth(_x), getNextCounter()) + ); +} + +void ProtoConverter::visit(AddressType const& _x) +{ + visitType( + DataType::VALUE, + getAddressTypeAsString(_x), + addressValueAsString(getNextCounter()) + ); +} + +void ProtoConverter::visit(FixedByteType const& _x) +{ + visitType( + DataType::VALUE, + getFixedByteTypeAsString(_x), + fixedByteValueAsString(getFixedByteWidth(_x), getNextCounter()) + ); +} + +void ProtoConverter::visit(ValueType const& _x) +{ + switch (_x.value_type_oneof_case()) + { + case ValueType::kInty: + visit(_x.inty()); + break; + case ValueType::kByty: + visit(_x.byty()); + break; + case ValueType::kAdty: + visit(_x.adty()); + break; + case ValueType::VALUE_TYPE_ONEOF_NOT_SET: + break; + } +} + +void ProtoConverter::visit(DynamicByteArrayType const& _x) +{ + visitType( + (_x.type() == DynamicByteArrayType::BYTES) ? DataType::BYTES : DataType::STRING, + bytesArrayTypeAsString(_x), + bytesArrayValueAsString() + ); +} + +// TODO: Implement struct visitor +void ProtoConverter::visit(StructType const&) +{ +} + +std::string ProtoConverter::arrayDimInfoAsString(ArrayDimensionInfo const& _x) +{ + unsigned arrLength = getArrayLengthFromFuzz(_x.length()); + if (_x.is_static()) + return Whiskers(R"([])") + ("length", std::to_string(arrLength)) + .render(); + else + return R"([])"; +} + +void ProtoConverter::arrayDimensionsAsStringVector( + ArrayType const& _x, + std::vector& _vecOfStr) +{ + solAssert(_x.info_size() > 0, "Proto ABIv2 Fuzzer: Array dimensions empty."); + for (auto const& dim: _x.info()) + _vecOfStr.push_back(arrayDimInfoAsString(dim)); +} + +ProtoConverter::VecOfBoolUnsigned ProtoConverter::arrayDimensionsAsPairVector( + ArrayType const& _x +) +{ + VecOfBoolUnsigned arrayDimsPairVector = {}; + for (auto const& dim: _x.info()) + arrayDimsPairVector.push_back(arrayDimInfoAsPair(dim)); + solAssert(!arrayDimsPairVector.empty(), "Proto ABIv2 Fuzzer: Array dimensions empty."); + return arrayDimsPairVector; +} + +std::string ProtoConverter::getValueByBaseType(ArrayType const& _x) +{ + switch (_x.base_type_oneof_case()) + { + case ArrayType::kInty: + return integerValueAsString(isIntSigned(_x.inty()), getIntWidth(_x.inty()), getNextCounter()); + case ArrayType::kByty: + return fixedByteValueAsString(getFixedByteWidth(_x.byty()), getNextCounter()); + case ArrayType::kAdty: + return addressValueAsString(getNextCounter()); + // TODO: Implement structs. + case ArrayType::kStty: + case ArrayType::BASE_TYPE_ONEOF_NOT_SET: + solAssert(false, "Proto ABIv2 fuzzer: Invalid array base type"); + } +} + +// Adds a resize operation for a given dimension of type `_type` and expression referenced +// by `_var`. `_isStatic` is true for statically sized dimensions, false otherwise. +// `_arrayLen` is equal to length of statically sized array dimension. For dynamically +// sized dimension, we use `getArrayLengthFromFuzz()` and a monotonically increasing +// counter to obtain actual length. Function returns dimension length. +unsigned ProtoConverter::resizeDimension( + bool _isStatic, + unsigned _arrayLen, + std::string const& _var, + std::string const& _param, + std::string const& _type +) +{ + unsigned length; + if (_isStatic) + length = _arrayLen; + else + { + length = getArrayLengthFromFuzz(_arrayLen, getNextCounter()); + + // If local var, new T(l); + // Else, l; + std::string lhs, rhs; + if (m_isStateVar) + { + lhs = _var + ".length"; + rhs = Whiskers(R"()") + ("length", std::to_string(length)) + .render(); + } + else + { + lhs = _var; + rhs = Whiskers(R"(new ())") + ("type", _type) + ("length", std::to_string(length)) + .render(); + } + // If local var, x = new T(l); + // Else, x.length = l; + addVarDef(lhs, rhs); + } + + // if (c.length != l) + checkResizeOp(_param, length); + return length; +} + +void ProtoConverter::resizeHelper( + ArrayType const& _x, + std::vector _arrStrVec, + VecOfBoolUnsigned _arrInfoVec, + std::string const& _varName, + std::string const& _paramName +) +{ + // Initialize value expressions if we have arrived at leaf node, + // (depth-first) recurse otherwise. + if (_arrInfoVec.empty()) + { + // expression name is _var + // value is a value of base type + std::string value = getValueByBaseType(_x); + // add assignment and check + addCheckedVarDef(DataType::VALUE, _varName, _paramName, value); + } + else + { + auto& dim = _arrInfoVec.back(); + + std::string type = std::accumulate( + _arrStrVec.begin(), + _arrStrVec.end(), + std::string("") + ); + unsigned length = resizeDimension(dim.first, dim.second, _varName, _paramName, type); + // Recurse one level dimension down. + _arrStrVec.pop_back(); + _arrInfoVec.pop_back(); + for (unsigned i = 0; i < length; i++) + resizeHelper( + _x, + _arrStrVec, + _arrInfoVec, + _varName + "[" + std::to_string(i) + "]", + _paramName + "[" + std::to_string(i) + "]" + ); + } +} + +// This function takes care of properly resizing and initializing ArrayType. +// In parallel, it adds runtime checks on array bound and values. +void ProtoConverter::resizeInitArray( + ArrayType const& _x, + std::string const& _baseType, + std::string const& _varName, + std::string const& _paramName +) +{ + VecOfBoolUnsigned arrInfoVec = arrayDimensionsAsPairVector(_x); + std::vector arrStrVec = {_baseType}; + arrayDimensionsAsStringVector(_x, arrStrVec); + resizeHelper(_x, arrStrVec, arrInfoVec, _varName, _paramName); +} + +// Returns array type from it's base type (e.g., int8) and array dimensions info contained in +// ArrayType. +std::string ProtoConverter::arrayTypeAsString(std::string const& _baseType, ArrayType const& _x) +{ + std::vector typeStringVec = {_baseType}; + arrayDimensionsAsStringVector(_x, typeStringVec); + + return std::accumulate( + typeStringVec.begin(), + typeStringVec.end(), + std::string("") + ); +} + +void ProtoConverter::visit(ArrayType const& _x) +{ + // Bail out if input contains too few or too many dimensions. + if (_x.info_size() == 0 || _x.info_size() > (int)s_maxArrayDimensions) + return; + + string baseType = {}; + switch (_x.base_type_oneof_case()) + { + case ArrayType::kInty: + baseType = getIntTypeAsString(_x.inty()); + break; + case ArrayType::kByty: + baseType = getFixedByteTypeAsString(_x.byty()); + break; + case ArrayType::kAdty: + baseType = getAddressTypeAsString(_x.adty()); + break; + case ArrayType::kStty: + case ArrayType::BASE_TYPE_ONEOF_NOT_SET: + return; + } + visitArrayType(baseType, _x); +} + +void ProtoConverter::visit(NonValueType const& _x) +{ + switch (_x.nonvalue_type_oneof_case()) + { + case NonValueType::kDynbytearray: + visit(_x.dynbytearray()); + break; + case NonValueType::kArrtype: + visit(_x.arrtype()); + break; + case NonValueType::kStype: + visit(_x.stype()); + break; + case NonValueType::NONVALUE_TYPE_ONEOF_NOT_SET: + break; + } +} + +void ProtoConverter::visit(Type const& _x) +{ + switch (_x.type_oneof_case()) + { + case Type::kVtype: + visit(_x.vtype()); + break; + case Type::kNvtype: + visit(_x.nvtype()); + break; + case Type::TYPE_ONEOF_NOT_SET: + break; + } +} + +void ProtoConverter::visit(VarDecl const& _x) +{ + 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; + } +} + +// 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(); + } +} + +// Function that is called by the factory contract +void ProtoConverter::visit(TestFunction const& _x) +{ + m_output << R"( + + function test() public returns (uint) { + )"; + + // Define state variables in function scope + m_output << m_statebuffer.str(); + + // TODO: Support more than one but less than N local variables + visit(_x.local_vars()); + + m_output << Whiskers(R"( + uint returnVal = this.coder_public(); + if (returnVal != 0) + return returnVal; + return (uint(1000) + this.coder_external()); + } + )") + ("parameter_names", YulUtilFunctions::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter)) + .render(); +} + +void ProtoConverter::writeHelperFunctions() +{ + m_output << 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; + } + )"; + + // 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 + m_output << Whiskers(R"( + function coder_public() public view returns (uint) { + + return 0; + } + + function coder_external() external view returns (uint) { + + return 0; + } + )") + ("parameters_memory", typedParametersAsString(CalleeType::PUBLIC)) + ("equality_checks", equalityChecksAsString()) + ("parameters_calldata", typedParametersAsString(CalleeType::EXTERNAL)) + .render(); +} + +void ProtoConverter::visit(Contract const& _x) +{ + m_output << R"(pragma solidity >=0.0; +pragma experimental ABIEncoderV2; + +contract Factory { + function test() external returns (uint) { + C c = new C(); + return c.test(); + } +} + +contract C { +)"; + // TODO: Support more than one but less than N state variables + visit(_x.state_vars()); + m_isStateVar = false; + // Test function + visit(_x.testfunction()); + writeHelperFunctions(); + m_output << "\n}"; +} + +string ProtoConverter::contractToString(Contract const& _input) +{ + visit(_input); + return m_output.str(); +} \ No newline at end of file diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h new file mode 100644 index 000000000..54c2d0ada --- /dev/null +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -0,0 +1,361 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +/** + * Template of the solidity test program generated by this converter is as follows: + * + * pragma solidity >=0.0; + * pragma experimental ABIEncoderV2; + * + * contract Factory { + * // Factory test function. Called by EVM client + * function test() external returns (uint) { + * C c = new C(); + * return c.test(); + * } + * } + * + * contract C { + * // State variable + * string x_0; + * // Test function. Called by Factory test function + * 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 (1000) for differentiation. + * return (uint(1000) + this.coder_external(x_0, x_1)); + * } + * + * // Utility functions + * 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 view 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 view returns (uint) { + * if (!stringCompare(c_0, "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d")) + * return 1; + * if (!bytesCompare(c_1, "1")) + * return 2; + * return 0; + * } + * } + */ + + +namespace dev +{ +namespace test +{ +namespace abiv2fuzzer +{ +class ProtoConverter +{ +public: + ProtoConverter(): + m_isStateVar(true), + m_counter(0), + m_varCounter(0), + m_returnValue(1) + {} + + ProtoConverter(ProtoConverter const&) = delete; + + ProtoConverter(ProtoConverter&&) = delete; + + std::string contractToString(Contract const& _input); + +private: + using VecOfBoolUnsigned = std::vector>; + + enum class Delimiter + { + ADD, + SKIP + }; + enum class CalleeType + { + PUBLIC, + EXTERNAL + }; + enum class DataType + { + BYTES, + STRING, + VALUE, + ARRAY + }; + + void visit(IntegerType const&); + + void visit(FixedByteType const&); + + void visit(AddressType const&); + + void visit(ArrayType const&); + + void visit(DynamicByteArrayType const&); + + void visit(StructType const&); + + void visit(ValueType const&); + + void visit(NonValueType const&); + + void visit(Type const&); + + void visit(VarDecl const&); + + void visit(TestFunction const&); + + void visit(Contract const&); + + std::string getValueByBaseType(ArrayType const&); + + void resizeInitArray( + ArrayType const& _x, + std::string const& _baseType, + std::string const& _varName, + std::string const& _paramName + ); + + unsigned resizeDimension( + bool _isStatic, + unsigned _arrayLen, + std::string const& _var, + std::string const& _param, + std::string const& _type + ); + + void resizeHelper( + ArrayType const& _x, + std::vector _arrStrVec, + VecOfBoolUnsigned _arrInfoVec, + std::string const& _varName, + std::string const& _paramName + ); + + // Utility functions + void appendChecks(DataType _type, std::string const& _varName, std::string const& _rhs); + + void addVarDef(std::string const& _varName, std::string const& _rhs); + + void addCheckedVarDef( + DataType _type, + std::string const& _varName, + std::string const& _paramName, + std::string const& _rhs + ); + + void appendTypedParams( + CalleeType _calleeType, + bool _isValueType, + std::string const& _typeString, + std::string const& _varName, + Delimiter _delimiter + ); + + void appendTypedParamsPublic( + bool _isValueType, + std::string const& _typeString, + std::string const& _varName, + Delimiter _delimiter = Delimiter::ADD + ); + + void appendTypedParamsExternal( + bool _isValueType, + std::string const& _typeString, + std::string const& _varName, + Delimiter _delimiter = Delimiter::ADD + ); + + void appendVarDeclToOutput( + std::string const& _type, + std::string const& _varName, + std::string const& _qualifier + ); + + void checkResizeOp(std::string const& _varName, unsigned _len); + + void visitType(DataType _dataType, std::string const& _type, std::string const& _value); + + void visitArrayType(std::string const&, ArrayType const&); + + void createDeclAndParamList( + std::string const& _type, + DataType _dataType, + std::string& _varName, + std::string& _paramName + ); + + std::string equalityChecksAsString(); + + std::string typedParametersAsString(CalleeType _calleeType); + + void writeHelperFunctions(); + + // Function definitions + // m_counter is used to derive values for typed variables + unsigned getNextCounter() + { + return m_counter++; + } + + // m_varCounter is used to derive declared and parameterized variable names + unsigned getNextVarCounter() + { + return m_varCounter++; + } + + // Accepts an unsigned counter and returns a pair of strings + // First string is declared name (s_varNamePrefix) + // Second string is parameterized name (s_paramPrefix) + auto newVarNames(unsigned _varCounter) + { + return std::make_pair( + s_varNamePrefix + std::to_string(_varCounter), + s_paramNamePrefix + std::to_string(_varCounter) + ); + } + + // String and bytes literals are derived by hashing a monotonically increasing + // counter and enclosing the said hash inside double quotes. + std::string bytesArrayValueAsString() + { + return "\"" + toHex(hashUnsignedInt(getNextCounter()), HexPrefix::DontAdd) + "\""; + } + + std::string getQualifier(DataType _dataType) + { + return ((isValueType(_dataType) || m_isStateVar) ? "" : "memory"); + } + + // Static declarations + static std::string structTypeAsString(StructType const& _x); + static std::string intValueAsString(unsigned _width, unsigned _counter); + static std::string uintValueAsString(unsigned _width, unsigned _counter); + static std::string integerValueAsString(bool _sign, unsigned _width, unsigned _counter); + static std::string addressValueAsString(unsigned _counter); + static std::string fixedByteValueAsString(unsigned _width, unsigned _counter); + static std::vector> arrayDimensionsAsPairVector(ArrayType const& _x); + static std::string arrayDimInfoAsString(ArrayDimensionInfo const& _x); + static void arrayDimensionsAsStringVector( + ArrayType const& _x, + std::vector& + ); + static std::string bytesArrayTypeAsString(DynamicByteArrayType const& _x); + static std::string arrayTypeAsString(std::string const&, ArrayType const&); + static std::string delimiterToString(Delimiter _delimiter); + + // Static function definitions + static bool isValueType(DataType _dataType) + { + return _dataType == DataType::VALUE; + } + + static unsigned getIntWidth(IntegerType const& _x) + { + return 8 * ((_x.width() % 32) + 1); + } + + static bool isIntSigned(IntegerType const& _x) + { + return _x.is_signed(); + } + + static std::string getIntTypeAsString(IntegerType const& _x) + { + return ((isIntSigned(_x) ? "int" : "uint") + std::to_string(getIntWidth(_x))); + } + + static unsigned getFixedByteWidth(FixedByteType const& _x) + { + return (_x.width() % 32) + 1; + } + + static std::string getFixedByteTypeAsString(FixedByteType const& _x) + { + return "bytes" + std::to_string(getFixedByteWidth(_x)); + } + + static std::string getAddressTypeAsString(AddressType const& _x) + { + return (_x.payable() ? "address payable": "address"); + } + + // Convert _counter to string and return its keccak256 hash + static u256 hashUnsignedInt(unsigned _counter) + { + return keccak256(h256(_counter)); + } + + static u256 maskUnsignedInt(unsigned _counter, unsigned _numMaskNibbles) + { + return hashUnsignedInt(_counter) & u256("0x" + std::string(_numMaskNibbles, 'f')); + } + + // Requires caller to pass number of nibbles (twice the number of bytes) as second argument. + // Note: Don't change HexPrefix::Add. See comment in fixedByteValueAsString(). + static std::string maskUnsignedIntToHex(unsigned _counter, unsigned _numMaskNibbles) + { + return toHex(maskUnsignedInt(_counter, _numMaskNibbles), HexPrefix::Add); + } + + static unsigned getArrayLengthFromFuzz(unsigned _fuzz, unsigned _counter = 0) + { + return ((_fuzz + _counter) % s_maxArrayLength) + 1; + } + + static std::pair arrayDimInfoAsPair(ArrayDimensionInfo const& _x) + { + return std::make_pair(_x.is_static(), getArrayLengthFromFuzz(_x.length())); + } + + /// Contains the test program + std::ostringstream m_output; + /// Temporary storage for state variable definitions + std::ostringstream m_statebuffer; + /// Contains a subset of the test program. This subset contains + /// checks to be encoded in the test program + std::ostringstream m_checks; + /// Contains typed parameter list to be passed to callee functions + std::ostringstream m_typedParamsExternal; + std::ostringstream m_typedParamsPublic; + /// Predicate that is true if we are in contract scope + bool m_isStateVar; + unsigned m_counter; + unsigned m_varCounter; + /// Monotonically increasing return value for error reporting + unsigned m_returnValue; + static unsigned constexpr s_maxArrayLength = 2; + static unsigned constexpr s_maxArrayDimensions = 10; + /// Prefixes for declared and parameterized variable names + static auto constexpr s_varNamePrefix = "x_"; + static auto constexpr s_paramNamePrefix = "c_"; +}; +} +} +}