From 088ebb4078a4a849d68e572b96c9559a0783aef7 Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Tue, 27 Jul 2021 22:23:35 -0500 Subject: [PATCH] [isoltest] Add support for fixed point types. --- test/libsolidity/SemanticTest.cpp | 1 + test/libsolidity/util/BytesUtils.cpp | 35 +++++++++++ test/libsolidity/util/BytesUtils.h | 9 +++ test/libsolidity/util/ContractABIUtils.cpp | 22 +++++++ test/libsolidity/util/SoltestTypes.h | 5 +- test/libsolidity/util/TestFileParser.cpp | 19 ++++-- test/libsolidity/util/TestFunctionCall.cpp | 67 ++++++++++++++++++++++ test/libsolidity/util/TestFunctionCall.h | 10 ++-- 8 files changed, 155 insertions(+), 13 deletions(-) diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 50d7dfcc0..0c0181339 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -461,6 +461,7 @@ TestCase::TestResult SemanticTest::runTest( test.setSideEffects(move(effects)); success &= test.call().expectedSideEffects == test.call().actualSideEffects; + success &= test.checkFixedPointTypes(); } if (!m_testCaseWantsYulRun && _isYulRun) diff --git a/test/libsolidity/util/BytesUtils.cpp b/test/libsolidity/util/BytesUtils.cpp index c3044d151..96d4b582b 100644 --- a/test/libsolidity/util/BytesUtils.cpp +++ b/test/libsolidity/util/BytesUtils.cpp @@ -96,6 +96,25 @@ bytes BytesUtils::convertNumber(string const& _literal) } } +bytes BytesUtils::convertFixedPoint(string const& _literal, size_t *_fixedPointN) +{ + solAssert(_fixedPointN != nullptr, ""); + solAssert(_literal.find('.') != string::npos, ""); + string v_integer{_literal}; + string v_fraction; + v_integer = _literal.substr(0, _literal.find('.')); + v_fraction = {_literal.substr(_literal.find('.') + 1)}; + try + { + *_fixedPointN = v_fraction.length(); + return util::fromHex(util::toHex(u256{v_integer + v_fraction})); + } + catch (std::exception const&) + { + BOOST_THROW_EXCEPTION(TestParserError("Number encoding invalid.")); + } +} + bytes BytesUtils::convertHexNumber(string const& _literal) { try @@ -206,6 +225,19 @@ string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff) return os.str(); } +std::string BytesUtils::formatFixedPoint(bytes const& _bytes, size_t _fixedPointM, size_t _fixedPointN) +{ + solAssert(_fixedPointM != 0, ""); + u256 max{boost::multiprecision::pow(u256{2}, static_cast(_fixedPointM))}; + solAssert(u256{_bytes} >= max, ""); + stringstream os; + os << u256{_bytes}; + string value{os.str()}; + if (_fixedPointN > 0) + value.insert(value.length() - _fixedPointN, "."); + return value; +} + string BytesUtils::formatRawBytes( bytes const& _bytes, solidity::frontend::test::ParameterList const& _parameters, @@ -296,6 +328,9 @@ string BytesUtils::formatBytes( case ABIType::String: os << formatString(_bytes, _bytes.size() - countRightPaddedZeros(_bytes)); break; + case ABIType::FixedPointType: + os << formatFixedPoint(_bytes, _abiType.M, _abiType.N); + break; case ABIType::Failure: break; case ABIType::None: diff --git a/test/libsolidity/util/BytesUtils.h b/test/libsolidity/util/BytesUtils.h index 5ec9f3271..e1d0f4352 100644 --- a/test/libsolidity/util/BytesUtils.h +++ b/test/libsolidity/util/BytesUtils.h @@ -54,6 +54,10 @@ public: /// representation of the decimal number literal. Throws if conversion fails. static bytes convertNumber(std::string const& _literal); + /// Tries to convert \param _literal to an unpadded `bytes` + /// representation of the decimal number literal. Throws if conversion fails. + static bytes convertFixedPoint(std::string const& _literal, size_t *_fixedPointN); + /// Tries to convert \param _literal to an unpadded `bytes` /// representation of the hex literal. Throws if conversion fails. static bytes convertHexNumber(std::string const& _literal); @@ -98,6 +102,11 @@ public: return formatString(_bytes, _bytes.size()); } + /// Converts \param _bytes to a soltest-compliant and human-readable + /// string representation of a byte array which is assumed to hold + /// a hexString value. + static std::string formatFixedPoint(bytes const& _bytes, size_t _fixedPointM, size_t _fixedPointN); + /// Used to print returned bytes from function calls to the commandline. /// Returns a string representation of given _bytes in ranges of 32 bytes. /// If _withSignature is true, the first 4 bytes will be formatted separately. diff --git a/test/libsolidity/util/ContractABIUtils.cpp b/test/libsolidity/util/ContractABIUtils.cpp index 2703ff441..fbb15d6ed 100644 --- a/test/libsolidity/util/ContractABIUtils.cpp +++ b/test/libsolidity/util/ContractABIUtils.cpp @@ -114,6 +114,11 @@ bool isFixedStringArray(string const& _type) return regex_match(_type, regex{"string\\[\\d+\\]"}); } +bool isFixedPointType(string const& _type) +{ + return regex_match(_type, regex{"fixed.*"}); +} + bool isTuple(string const& _type) { return _type == "tuple"; @@ -245,6 +250,23 @@ bool ContractABIUtils::appendTypesFromName( _dynamicTypes.push_back(ABIType{ABIType::String, ABIType::AlignLeft}); } } + else if (isFixedPointType(type)) + { + vector fixedTypeParameter; + solAssert(boost::starts_with(type, "fixed"), ""); + string fixedTypeParameterString{type.substr(5)}; + solAssert(fixedTypeParameterString.find('x') != string::npos, ""); + boost::split(fixedTypeParameter, fixedTypeParameterString, boost::is_any_of("x")); + solAssert(fixedTypeParameter.size() == 2, ""); + ABIType abiType{ABIType::FixedPointType}; + abiType.M = static_cast(std::stoi(fixedTypeParameter[0])); + abiType.N = static_cast(std::stoi(fixedTypeParameter[1])); + solAssert( + 8 <= abiType.M && abiType.M <= 256 && abiType.M % 8 == 0 && abiType.N <= 80, + "" + ); + _inplaceTypes.push_back(abiType); + } else if (isBytes(type)) return false; else if (isFixedTupleArray(type)) diff --git a/test/libsolidity/util/SoltestTypes.h b/test/libsolidity/util/SoltestTypes.h index f1b284135..d2730d8eb 100644 --- a/test/libsolidity/util/SoltestTypes.h +++ b/test/libsolidity/util/SoltestTypes.h @@ -107,7 +107,8 @@ struct ABIType SignedDec, Hex, HexString, - String + String, + FixedPointType }; enum Align { @@ -125,6 +126,8 @@ struct ABIType Type type = ABIType::None; Align align = ABIType::AlignRight; size_t size = 32; + size_t M = 0; + size_t N = 0; bool alignDeclared = false; }; diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index 169a5a5cf..de40a93a2 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -407,11 +407,18 @@ Parameter TestFileParser::parseParameter() if (isSigned) parsed = "-" + parsed; - parameter.rawBytes = BytesUtils::applyAlign( - parameter.alignment, - parameter.abiType, - BytesUtils::convertNumber(parsed) - ); + if (parsed.find('.') == string::npos) + parameter.rawBytes = BytesUtils::applyAlign( + parameter.alignment, + parameter.abiType, + BytesUtils::convertNumber(parsed) + ); + else + { + parameter.abiType.type = ABIType::FixedPointType; + // note that parameter.abiType.M & parameter.abiType.N are not set here. + parameter.rawBytes = BytesUtils::convertFixedPoint(parsed, ¶meter.abiType.N); + } } else if (accept(Token::Failure, true)) { @@ -667,7 +674,7 @@ string TestFileParser::Scanner::scanDecimalNumber() { string number; number += current(); - while (langutil::isDecimalDigit(peek())) + while (langutil::isDecimalDigit(peek()) || '.' == peek()) { advance(); number += current(); diff --git a/test/libsolidity/util/TestFunctionCall.cpp b/test/libsolidity/util/TestFunctionCall.cpp index 65a7bd69d..d1f3e7e71 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -18,8 +18,11 @@ #include #include +#include +#include #include +#include #include #include @@ -266,6 +269,7 @@ string TestFunctionCall::formatBytesParameters( if (preferredParams) { ContractABIUtils::overwriteParameters(_errorReporter, preferredParams.value(), abiParams.value()); + preferredParams.value().begin()->abiType = abiParams.value().begin()->abiType; os << BytesUtils::formatBytesRange(_bytes, preferredParams.value(), _highlight); } } @@ -373,3 +377,66 @@ bool TestFunctionCall::matchesExpectation() const { return m_failure == m_call.expectations.failure && m_rawBytes == m_call.expectations.rawBytes(); } + +bool TestFunctionCall::checkFixedPointTypes() +{ + // if constructor, we do not have any type information, because of missing signature. + if (m_call.signature.find("constructor()") != string::npos) + return true; + + // check fixed point type used in return type. + ErrorReporter errorReporter; + std::optional returnType + = ContractABIUtils::parametersFromJsonOutputs(errorReporter, m_contractABI, m_call.signature); + if (returnType.has_value() && !returnType.value().empty() && returnType.value().begin()->abiType.type == ABIType::FixedPointType) + if (m_call.expectations.result.begin()->abiType.N != returnType.value().begin()->abiType.N) + { + // if not matching, we reset the raw bytes of result, so that isoltest will generate the correct expectation. + m_call.expectations.result.begin()->rawBytes = {}; + return false; + } + + // check fixed point types used in arguments. + // - extract argument types from signature + size_t ob = m_call.signature.find('(') + 1; + size_t cb = m_call.signature.find(')'); + string signature = m_call.signature.substr(ob, cb - ob); + vector inputTypes; + if (signature.find(',') != string::npos) + { + if (!signature.empty()) + boost::split(inputTypes, signature, boost::is_any_of(",")); + } + else if (!signature.empty()) + inputTypes.emplace_back(signature); + + if (m_call.arguments.parameters.size() != inputTypes.size()) + return true; + + // - iterate through arguments and check that fractional digits match with the corresponding argument type. + for (size_t i = 0; i < inputTypes.size(); ++i) + { + auto& type = inputTypes[i]; + auto& parameter = m_call.arguments.parameters[i]; + if ((parameter.abiType.type == ABIType::FixedPointType || parameter.abiType.type == ABIType::UnsignedDec) && (type.find("fixed") != string::npos)) + { + size_t fixedPointN; + vector fixedTypeParameter; + string fixedTypeParameterString{type.substr(5)}; + solAssert(fixedTypeParameterString.find('x') != string::npos, ""); + boost::split(fixedTypeParameter, fixedTypeParameterString, boost::is_any_of("x")); + solAssert(fixedTypeParameter.size() == 2, ""); + fixedPointN = static_cast(std::stoi(fixedTypeParameter[1])); + string what; + if (parameter.rawString.find('.') != string::npos) + { + what = parameter.rawString.substr(parameter.rawString.find('.') + 1); + solAssert(fixedPointN == what.length(), type + " not compatible with '" + parameter.rawString + "'"); + } + else + solAssert(fixedPointN == 0, type + " not compatible with '" + parameter.rawString + "'"); + } + } + + return true; +} diff --git a/test/libsolidity/util/TestFunctionCall.h b/test/libsolidity/util/TestFunctionCall.h index c54a440ac..146baf3ea 100644 --- a/test/libsolidity/util/TestFunctionCall.h +++ b/test/libsolidity/util/TestFunctionCall.h @@ -20,9 +20,11 @@ #include #include #include +#include #include +#include "ContractABIUtils.h" #include #include #include @@ -95,6 +97,8 @@ public: void setContractABI(Json::Value _contractABI) { m_contractABI = std::move(_contractABI); } void setSideEffects(std::vector _sideEffects) { m_call.actualSideEffects = _sideEffects; } + bool checkFixedPointTypes(); + private: /// Tries to format the given `bytes`, applying the detected ABI types that have be set for each parameter. /// Throws if there's a mismatch in the size of `bytes` and the desired formats that are specified @@ -109,12 +113,6 @@ private: bool failure = false ) const; - /// Formats a given _bytes applying the _abiType. - std::string formatBytesRange( - bytes const& _bytes, - ABIType const& _abiType - ) const; - /// Formats a FAILURE plus additional parameters, if e.g. a revert message was returned. std::string formatFailure( ErrorReporter& _errorReporter,