[isoltest] Add support for fixed point types.

This commit is contained in:
Alexander Arlt 2021-07-27 22:23:35 -05:00
parent fac1c81ab4
commit 088ebb4078
8 changed files with 155 additions and 13 deletions

View File

@ -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)

View File

@ -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<unsigned>(_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:

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 *_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.

View File

@ -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<string> 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<unsigned>(std::stoi(fixedTypeParameter[0]));
abiType.N = static_cast<unsigned>(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))

View File

@ -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;
};

View File

@ -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, &parameter.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();

View File

@ -18,8 +18,11 @@
#include <test/libsolidity/util/ContractABIUtils.h>
#include <libsolutil/AnsiColorized.h>
#include <libsolutil/JSON.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <optional>
#include <stdexcept>
@ -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<ParameterList> 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<string> 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<string> 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<unsigned>(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;
}

View File

@ -20,9 +20,11 @@
#include <liblangutil/Exceptions.h>
#include <libsolutil/AnsiColorized.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/JSON.h>
#include <json/json.h>
#include "ContractABIUtils.h"
#include <iosfwd>
#include <numeric>
#include <stdexcept>
@ -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 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,