[isoltest] Add support for fixed point types.

This commit is contained in:
Alexander Arlt
2021-08-04 11:19:20 -05:00
parent ebd8dd2a99
commit 0e6a0769fa
9 changed files with 247 additions and 27 deletions
+51 -7
View File
@@ -17,18 +17,13 @@
// SPDX-License-Identifier: GPL-3.0
#include <test/libsolidity/util/BytesUtils.h>
#include <test/libsolidity/util/ContractABIUtils.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <liblangutil/Common.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/StringUtils.h>
#include <boost/algorithm/string.hpp>
#include <fstream>
#include <iomanip>
#include <memory>
#include <regex>
@@ -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<u256>(_bytes))};
if (signedValue < 0)
{
negative = true;
signedValue = -signedValue;
}
value = signedValue.str();
}
else
value = fromBigEndian<u256>(_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<long>(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,
+8
View File
@@ -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.
+22 -1
View File
@@ -20,6 +20,8 @@
#include <test/libsolidity/util/SoltestErrors.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolutil/FunctionSelector.h>
#include <libsolutil/CommonData.h>
@@ -124,6 +126,21 @@ bool isFixedTupleArray(string const& _type)
return regex_match(_type, regex{"tuple\\[\\d+\\]"});
}
optional<ABIType> isFixedPoint(string const& type)
{
optional<ABIType> 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<unsigned>(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<ABIType> 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.");
+6 -1
View File
@@ -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;
};
+12 -6
View File
@@ -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();
+15 -6
View File
@@ -19,7 +19,7 @@
#include <libsolutil/AnsiColorized.h>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string.hpp>
#include <optional>
#include <stdexcept>
@@ -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<ParameterList> 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<ParameterList> 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;
}
+6 -6
View File
@@ -16,10 +16,12 @@
#include <test/libsolidity/util/TestFileParser.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <test/libsolidity/util/ContractABIUtils.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/AnsiColorized.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/JSON.h>
#include <json/json.h>
@@ -95,6 +97,8 @@ public:
void setContractABI(Json::Value _contractABI) { m_contractABI = std::move(_contractABI); }
void setSideEffects(std::vector<std::string> _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;