#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 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; * * 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; * } * * /// 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) { * ... * } * * /// 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 { namespace abiv2fuzzer { class ProtoConverter { public: ProtoConverter(): m_isStateVar(true), m_counter(0), m_varCounter(0), m_returnValue(1), m_isLastParamRightPadded(false) {} 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(BoolType const&); 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&); DataType getDataTypeByBaseType(ArrayType const& _x); 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) ); } std::string getQualifier(DataType _dataType) { return ((isValueType(_dataType) || m_isStateVar) ? "" : "memory"); } bool isLastParamRightPadded() { return m_isLastParamRightPadded; } // Static declarations static std::string structTypeAsString(StructType const& _x); static std::string boolValueAsString(unsigned _counter); 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); /// Returns a hex literal if _isHexLiteral is true, a string literal otherwise. /// The size of the returned literal is _numBytes bytes. /// @param _decorate If true, the returned string is enclosed within double quotes /// if _isHexLiteral is false. /// @param _isHexLiteral If true, the returned string is enclosed within /// double quotes prefixed by the string "hex" if _decorate is true. If /// _decorate is false, the returned string is returned as-is. /// @return hex value as string static std::string hexValueAsString( unsigned _numBytes, unsigned _counter, bool _isHexLiteral, bool _decorate = true ); /// Concatenates the hash value obtained from monotonically increasing counter /// until the desired number of bytes determined by _numBytes. /// @param _width Desired number of bytes for hex value /// @param _counter A counter value used for creating a keccak256 hash /// @param _isHexLiteral Since this routine may be used to construct /// string or hex literals, this flag is used to construct a valid output. /// @return Valid hex or string literal of size _width bytes static std::string variableLengthValueAsString( unsigned _width, unsigned _counter, bool _isHexLiteral ); 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 std::string croppedString(unsigned _numBytes, unsigned _counter, bool _isHexLiteral); // 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 getBoolTypeAsString() { return "bool"; } 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"); } static DataType getDataTypeOfDynBytesType(DynamicByteArrayType const& _x) { if (_x.type() == DynamicByteArrayType::STRING) return DataType::STRING; else return DataType::BYTES; } /// Returns true if input is either a string or bytes, false otherwise. static bool isDataTypeBytesOrString(DataType _type) { return _type == DataType::STRING || _type == DataType::BYTES; } // 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); } /// Dynamically sized arrays can have a length of at least zero /// and at most s_maxArrayLength. static unsigned getDynArrayLengthFromFuzz(unsigned _fuzz, unsigned _counter) { // Increment modulo value by one in order to meet upper bound return (_fuzz + _counter) % (s_maxArrayLength + 1); } /// Statically sized arrays must have a length of at least one /// and at most s_maxArrayLength. static unsigned getStaticArrayLengthFromFuzz(unsigned _fuzz) { return _fuzz % s_maxArrayLength + 1; } static std::pair arrayDimInfoAsPair(ArrayDimensionInfo const& _x) { return ( _x.is_static() ? std::make_pair(true, getStaticArrayLengthFromFuzz(_x.length())) : std::make_pair(false, getDynArrayLengthFromFuzz(_x.length(), 0)) ); } /// Returns a pseudo-random value for the size of a string/hex /// literal. Used for creating variable length hex/string literals. /// @param _counter Monotonically increasing counter value static unsigned getVarLength(unsigned _counter) { // Since _counter values are usually small, we use // 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); } /// Returns a hex/string literal of variable length whose value and /// size are pseudo-randomly determined from the counter value. /// @param _counter A monotonically increasing counter value /// @param _isHexLiteral Flag that indicates whether hex (if true) or /// string literal (false) is desired /// @return A variable length hex/string value static std::string bytesArrayValueAsString(unsigned _counter, bool _isHexLiteral) { return variableLengthValueAsString( getVarLength(_counter), _counter, _isHexLiteral ); } /// 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; /// Flag that indicates if last parameter passed to a function call /// is of a type that is going to be right padded by the ABI /// encoder. bool m_isLastParamRightPadded; static unsigned constexpr s_maxArrayLength = 4; static unsigned constexpr s_maxArrayDimensions = 4; static unsigned constexpr s_maxDynArrayLength = 256; /// Prefixes for declared and parameterized variable names static auto constexpr s_varNamePrefix = "x_"; static auto constexpr s_paramNamePrefix = "c_"; }; } } }