mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[isoltest] Add support for fixed point types.
This commit is contained in:
parent
ebd8dd2a99
commit
0e6a0769fa
@ -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<string> effects;
|
||||
|
124
test/libsolidity/semanticTests/fixedPoint/inline_assembly.sol
Normal file
124
test/libsolidity/semanticTests/fixedPoint/inline_assembly.sol
Normal file
@ -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
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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.");
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user