From 0e6a0769fa76ef6a6b5e9b965b84c5bc5be30f7b Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Tue, 27 Jul 2021 09:32:34 -0500 Subject: [PATCH] [isoltest] Add support for fixed point types. --- test/libsolidity/SemanticTest.cpp | 3 + .../fixedPoint/inline_assembly.sol | 124 ++++++++++++++++++ test/libsolidity/util/BytesUtils.cpp | 58 +++++++- test/libsolidity/util/BytesUtils.h | 8 ++ test/libsolidity/util/ContractABIUtils.cpp | 23 +++- test/libsolidity/util/SoltestTypes.h | 7 +- test/libsolidity/util/TestFileParser.cpp | 18 ++- test/libsolidity/util/TestFunctionCall.cpp | 21 ++- test/libsolidity/util/TestFunctionCall.h | 12 +- 9 files changed, 247 insertions(+), 27 deletions(-) create mode 100644 test/libsolidity/semanticTests/fixedPoint/inline_assembly.sol diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 50d7dfcc0..54a49e405 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -453,6 +453,9 @@ TestCase::TestResult SemanticTest::runTest( test.setFailure(!m_transactionSuccessful); test.setRawBytes(move(output)); test.setContractABI(m_compiler.contractABI(m_compiler.lastContractName(m_sources.mainSourceFile))); + + if (!test.validFractionDigits()) + success = false; } vector effects; diff --git a/test/libsolidity/semanticTests/fixedPoint/inline_assembly.sol b/test/libsolidity/semanticTests/fixedPoint/inline_assembly.sol new file mode 100644 index 000000000..ff37894a8 --- /dev/null +++ b/test/libsolidity/semanticTests/fixedPoint/inline_assembly.sol @@ -0,0 +1,124 @@ +contract A { + function s128x18() public pure returns (fixed x) { + assembly {x := 1000000111000222000333} + } + + function s128x16() public pure returns (fixed128x16 x) { + assembly {x := 1000000111000222000333} + } + + function s128x8() public pure returns (fixed128x8 x) { + assembly {x := 1000000111000222000333} + } + + function s128x4() public pure returns (fixed128x4 x) { + assembly {x := 1000000111000222000333} + } + + function s128x2() public pure returns (fixed128x2 x) { + assembly {x := 1000000111000222000333} + } + + function s128x0() public pure returns (fixed128x0 x) { + assembly {x := 1000000111000222000333} + } + + function s128x0_2() public pure returns (fixed128x0 x, fixed128x2 y) { + assembly { + x := 1000000111000222000333 + y := 1000000111000222000333 + } + } + + function s128x2(fixed128x2 i) public pure returns (fixed128x2 x) { + assembly {x := i} + } + + function u128x2(ufixed128x2 i) public pure returns (ufixed128x2 x) { + assembly {x := i} + } + + function u128x0(ufixed128x0 i) public pure returns (ufixed128x0 x) { + assembly {x := i} + } + + function s128x2_add(fixed128x2 a, fixed128x2 b) public pure returns (fixed128x2 x) { + assembly {x := add(a, b)} + } + + function s128x0(fixed128x0 i) public pure returns (fixed128x0 x) { + assembly {x := i} + } + + function s32x0(fixed32x0 i) public pure returns (fixed32x0 x) { + assembly {x := i} + } + + function s16x0(fixed16x0 i) public pure returns (fixed16x0 x) { + assembly {x := i} + } + + function s8x0(fixed8x0 i) public pure returns (fixed8x0 x) { + assembly {x := i} + } + + function u32x0(ufixed32x0 i) public pure returns (ufixed32x0 x) { + assembly {x := i} + } + + function u16x0(ufixed16x0 i) public pure returns (ufixed16x0 x) { + assembly {x := i} + } + + function u8x0(ufixed8x0 i) public pure returns (ufixed8x0 x) { + assembly {x := i} + } + + function u32x2(ufixed32x2 i) public pure returns (ufixed32x2 x) { + assembly {x := i} + } + + function u16x2(ufixed16x2 i) public pure returns (ufixed16x2 x) { + assembly {x := i} + } + + function u8x2(ufixed8x2 i) public pure returns (ufixed8x2 x) { + assembly {x := i} + } + + function u32168x2(ufixed32x2 a, ufixed16x2 b, ufixed8x2 c) public pure returns (ufixed32x2 x) { + assembly {x := a} + } +} +// ==== +// compileToEwasm: also +// compileViaYul: also +// ---- +// u128x0(ufixed128x0): 123 -> 123 +// u128x2(ufixed128x2): 1.23 -> 1.23 +// s128x2(fixed128x2): -2.34 -> -2.34 +// s128x2(fixed128x2): 2.34 -> 2.34 +// u128x0(ufixed128x0): 123 -> 123 +// s128x0(fixed128x0): -234 -> -234 +// s128x2_add(fixed128x2,fixed128x2): -1.23, -2.34 -> -3.57 +// s128x2_add(fixed128x2,fixed128x2): -1.23, 2.34 -> 1.11 +// s128x2_add(fixed128x2,fixed128x2): 1.23, -2.34 -> -1.11 +// s128x2_add(fixed128x2,fixed128x2): 1.23, 2.34 -> 3.57 +// s128x2_add(fixed128x2,fixed128x2): 1701411834604692317316873037158841057.27, -1701411834604692317316873037158841057.28 -> -0.01 +// u32x0(ufixed32x0): 0 -> 0 +// u16x0(ufixed16x0): 0 -> 0 +// u8x0(ufixed8x0): 0 -> 0 +// u32x0(ufixed32x0): 4294967295 -> 4294967295 +// u16x0(ufixed16x0): 65535 -> 65535 +// u8x0(ufixed8x0): 255 -> 255 +// u32x2(ufixed32x2): 42949672.95 -> 42949672.95 +// u16x2(ufixed16x2): 655.35 -> 655.35 +// u8x2(ufixed8x2): 2.55 -> 2.55 +// u32168x2(ufixed32x2,ufixed16x2,ufixed8x2): 42949672.95, 655.35, 2.55 -> 42949672.95 +// s128x18() -> 1000.000111000222000333 +// s128x16() -> 100000.0111000222000333 +// s128x8() -> 10000001110002.22000333 +// s128x4() -> 100000011100022200.0333 +// s128x2() -> 10000001110002220003.33 +// s128x0() -> 1000000111000222000333 +// s128x0_2() -> 1000000111000222000333, 10000001110002220003.33 diff --git a/test/libsolidity/util/BytesUtils.cpp b/test/libsolidity/util/BytesUtils.cpp index c3044d151..94b7851d8 100644 --- a/test/libsolidity/util/BytesUtils.cpp +++ b/test/libsolidity/util/BytesUtils.cpp @@ -17,18 +17,13 @@ // SPDX-License-Identifier: GPL-3.0 #include - #include #include -#include - #include -#include #include -#include #include #include #include @@ -96,6 +91,22 @@ bytes BytesUtils::convertNumber(string const& _literal) } } +bytes BytesUtils::convertFixedPoint(string const& _literal, size_t& o_fractionalDigits) +{ + size_t dotPos = _literal.find('.'); + string valueInteger = _literal.substr(0, dotPos); + string valueFraction = _literal.substr(dotPos + 1); + o_fractionalDigits = valueFraction.length(); + try + { + return util::toBigEndian(u256(valueInteger + valueFraction)); + } + catch (std::exception const&) + { + BOOST_THROW_EXCEPTION(TestParserError("Number encoding invalid.")); + } +} + bytes BytesUtils::convertHexNumber(string const& _literal) { try @@ -206,6 +217,31 @@ string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff) return os.str(); } +std::string BytesUtils::formatFixedPoint(bytes const& _bytes, bool _signed, size_t _fractionalDigits) +{ + string value; + bool negative = false; + if (_signed) + { + s256 signedValue{u2s(fromBigEndian(_bytes))}; + if (signedValue < 0) + { + negative = true; + signedValue = -signedValue; + } + value = signedValue.str(); + } + else + value = fromBigEndian(_bytes).str(); + if (_fractionalDigits > 0) + { + if (_fractionalDigits > value.length()) + value = string(_fractionalDigits - value.length() + 1, '0') + value; + value.insert(value.length() - _fractionalDigits, "."); + } + return (negative ? "-" : "") + value; +} + string BytesUtils::formatRawBytes( bytes const& _bytes, solidity::frontend::test::ParameterList const& _parameters, @@ -296,8 +332,11 @@ string BytesUtils::formatBytes( case ABIType::String: os << formatString(_bytes, _bytes.size() - countRightPaddedZeros(_bytes)); break; - case ABIType::Failure: + case ABIType::UnsignedFixedPoint: + case ABIType::SignedFixedPoint: + os << formatFixedPoint(_bytes, _abiType.type == ABIType::SignedFixedPoint, _abiType.fractionalDigits); break; + case ABIType::Failure: case ABIType::None: break; } @@ -327,7 +366,12 @@ string BytesUtils::formatBytesRange( { bytes byteRange{it, it + static_cast(parameter.abiType.size)}; - if (!parameter.matchesBytes(byteRange)) + if (!parameter.matchesBytes(byteRange) || + // Fixed point values may look exactly the same, but their actual values may be different if + // it uses a different fixed point type (e.g. 0xb is 11, but it could also be 1.1). + // That's why we always enforce reformatting fixed point types here. + parameter.abiType.type == ABIType::UnsignedFixedPoint || + parameter.abiType.type == ABIType::SignedFixedPoint) AnsiColorized( os, _highlight, diff --git a/test/libsolidity/util/BytesUtils.h b/test/libsolidity/util/BytesUtils.h index 5ec9f3271..a5a020821 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& o_fractionalDigits); + /// 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,10 @@ public: return formatString(_bytes, _bytes.size()); } + /// Converts \param _bytes to a soltest-compliant and human-readable + /// decimal string representation of a byte array. Format of \param _bytes is binary. + static std::string formatFixedPoint(bytes const& _bytes, bool _signed, size_t _fractionalDigits); + /// 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..ebf93e780 100644 --- a/test/libsolidity/util/ContractABIUtils.cpp +++ b/test/libsolidity/util/ContractABIUtils.cpp @@ -20,6 +20,8 @@ #include +#include +#include #include #include @@ -124,6 +126,21 @@ bool isFixedTupleArray(string const& _type) return regex_match(_type, regex{"tuple\\[\\d+\\]"}); } +optional isFixedPoint(string const& type) +{ + optional fixedPointType; + smatch matches; + if (regex_match(type, matches, regex{"(u?)fixed(\\d+)x(\\d+)"})) + { + ABIType abiType(ABIType::SignedFixedPoint); + if (matches[1].str() == "u") + abiType.type = ABIType::UnsignedFixedPoint; + abiType.fractionalDigits = static_cast(std::stoi(matches[3].str())); + fixedPointType = abiType; + } + return fixedPointType; +} + string functionSignatureFromABI(Json::Value const& _functionABI) { auto inputs = _functionABI["inputs"]; @@ -200,6 +217,7 @@ bool ContractABIUtils::appendTypesFromName( ) { string type = _functionOutput["type"].asString(); + optional fixedPointType = isFixedPoint(type); if (isBool(type)) _inplaceTypes.push_back(ABIType{ABIType::Boolean}); else if (isUint(type)) @@ -245,6 +263,8 @@ bool ContractABIUtils::appendTypesFromName( _dynamicTypes.push_back(ABIType{ABIType::String, ABIType::AlignLeft}); } } + else if (fixedPointType.has_value()) + _inplaceTypes.push_back(fixedPointType.value()); else if (isBytes(type)) return false; else if (isFixedTupleArray(type)) @@ -270,7 +290,8 @@ void ContractABIUtils::overwriteParameters( { if ( _a.abiType.size != _b.abiType.size || - _a.abiType.type != _b.abiType.type + _a.abiType.type != _b.abiType.type || + _a.abiType.fractionalDigits != _b.abiType.fractionalDigits ) { _errorReporter.warning("Type or size of parameter(s) does not match."); diff --git a/test/libsolidity/util/SoltestTypes.h b/test/libsolidity/util/SoltestTypes.h index f1b284135..bd954f042 100644 --- a/test/libsolidity/util/SoltestTypes.h +++ b/test/libsolidity/util/SoltestTypes.h @@ -107,7 +107,9 @@ struct ABIType SignedDec, Hex, HexString, - String + String, + UnsignedFixedPoint, + SignedFixedPoint }; enum Align { @@ -125,6 +127,9 @@ struct ABIType Type type = ABIType::None; Align align = ABIType::AlignRight; size_t size = 32; + + size_t fractionalDigits = 0; + bool alignDeclared = false; }; diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index 169a5a5cf..65a7e9c0f 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -407,11 +407,17 @@ 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 = isSigned ? ABIType::SignedFixedPoint : ABIType::UnsignedFixedPoint; + parameter.rawBytes = BytesUtils::convertFixedPoint(parsed, parameter.abiType.fractionalDigits); + } } else if (accept(Token::Failure, true)) { @@ -667,7 +673,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..761156bd5 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -19,7 +19,7 @@ #include -#include +#include #include #include @@ -42,7 +42,7 @@ string TestFunctionCall::format( { stringstream stream; - bool highlight = !matchesExpectation() && _highlight; + bool highlight = !valid() && _highlight; auto formatOutput = [&](bool const _singleLine) { @@ -96,7 +96,7 @@ string TestFunctionCall::format( if (m_call.omitsArrow) { - if (_renderMode == RenderMode::ActualValuesExpectedGas && (m_failure || !matchesExpectation())) + if (_renderMode == RenderMode::ActualValuesExpectedGas && (m_failure || !valid())) stream << ws << arrow; } else @@ -132,8 +132,7 @@ string TestFunctionCall::format( bytes output = m_rawBytes; bool const isFailure = m_failure; result = isFailure ? - formatFailure(_errorReporter, m_call, output, _renderMode == RenderMode::ActualValuesExpectedGas, highlight) : - matchesExpectation() ? + formatFailure(_errorReporter, m_call, output, _renderMode == RenderMode::ActualValuesExpectedGas, highlight) : valid() ? formatRawParameters(m_call.expectations.result) : formatBytesParameters( _errorReporter, @@ -143,7 +142,7 @@ string TestFunctionCall::format( highlight ); - if (!matchesExpectation()) + if (!valid()) { std::optional abiParams; @@ -373,3 +372,13 @@ bool TestFunctionCall::matchesExpectation() const { return m_failure == m_call.expectations.failure && m_rawBytes == m_call.expectations.rawBytes(); } + +bool TestFunctionCall::validFractionDigits() const +{ + ErrorReporter errorReporter; + std::optional returnType = + ContractABIUtils::parametersFromJsonOutputs(errorReporter, m_contractABI, m_call.signature); + if (returnType.has_value() && !returnType.value().empty() && !m_call.expectations.result.empty()) + return m_call.expectations.result.begin()->abiType.fractionalDigits == returnType.value().begin()->abiType.fractionalDigits; + return true; +} diff --git a/test/libsolidity/util/TestFunctionCall.h b/test/libsolidity/util/TestFunctionCall.h index c54a440ac..8904d15ff 100644 --- a/test/libsolidity/util/TestFunctionCall.h +++ b/test/libsolidity/util/TestFunctionCall.h @@ -16,10 +16,12 @@ #include #include +#include #include #include #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 validFractionDigits() const; + 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, @@ -137,6 +135,8 @@ private: bool _showDifference ) const; + bool valid() const { return matchesExpectation() && validFractionDigits(); } + /// Compares raw expectations (which are converted to a byte representation before), /// and also the expected transaction status of the function call to the actual test results. bool matchesExpectation() const;