Merge pull request #7048 from ethereum/soltest-refactoring

[isoltest] Types and formatting refactoring
This commit is contained in:
chriseth 2019-07-09 12:50:15 +02:00 committed by GitHub
commit 479b843067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 779 additions and 465 deletions

View File

@ -0,0 +1,198 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <test/libsolidity/util/BytesUtils.h>
#include <liblangutil/Common.h>
#include <boost/algorithm/string.hpp>
#include <fstream>
#include <memory>
#include <regex>
#include <stdexcept>
using namespace dev;
using namespace langutil;
using namespace solidity;
using namespace dev::solidity::test;
using namespace std;
using namespace soltest;
bytes BytesUtils::convertBoolean(string const& _literal)
{
if (_literal == "true")
return bytes{true};
else if (_literal == "false")
return bytes{false};
else
throw Error(Error::Type::ParserError, "Boolean literal invalid.");
}
bytes BytesUtils::convertNumber(string const& _literal)
{
try
{
return toCompactBigEndian(u256{_literal});
}
catch (std::exception const&)
{
throw Error(Error::Type::ParserError, "Number encoding invalid.");
}
}
bytes BytesUtils::convertHexNumber(string const& _literal)
{
try
{
if (_literal.size() % 2)
throw Error(Error::Type::ParserError, "Hex number encoding invalid.");
else
return fromHex(_literal);
}
catch (std::exception const&)
{
throw Error(Error::Type::ParserError, "Hex number encoding invalid.");
}
}
bytes BytesUtils::convertString(string const& _literal)
{
try
{
return asBytes(_literal);
}
catch (std::exception const&)
{
throw Error(Error::Type::ParserError, "String encoding invalid.");
}
}
string BytesUtils::formatUnsigned(bytes const& _bytes) const
{
stringstream os;
if (*_bytes.begin() & 0x80)
os << u2s(fromBigEndian<u256>(_bytes));
else
os << fromBigEndian<u256>(_bytes);
return os.str();
}
string BytesUtils::formatSigned(bytes const& _bytes) const
{
stringstream os;
if (*_bytes.begin() & 0x80)
os << u2s(fromBigEndian<u256>(_bytes));
else
os << fromBigEndian<u256>(_bytes);
return os.str();
}
string BytesUtils::formatBoolean(bytes const& _bytes) const
{
stringstream os;
u256 result = fromBigEndian<u256>(_bytes);
if (result == 0)
os << "false";
else if (result == 1)
os << "true";
else
os << result;
return os.str();
}
string BytesUtils::formatHex(bytes const& _bytes) const
{
stringstream os;
string hex{toHex(_bytes, HexPrefix::Add)};
boost::algorithm::replace_all(hex, "00", "");
os << hex;
return os.str();
}
string BytesUtils::formatHexString(bytes const& _bytes) const
{
stringstream os;
os << "hex\"" << toHex(_bytes) << "\"";
return os.str();
}
string BytesUtils::formatString(bytes const& _bytes) const
{
stringstream os;
os << "\"";
bool expectZeros = false;
for (auto const& v: _bytes)
{
if (expectZeros && v != 0)
return {};
if (v == 0) expectZeros = true;
else
{
if (!isprint(v) || v == '"')
return {};
os << v;
}
}
os << "\"";
return os.str();
}
bytes BytesUtils::alignLeft(bytes _bytes) const
{
return std::move(_bytes) + bytes(32 - _bytes.size(), 0);
}
bytes BytesUtils::alignRight(bytes _bytes) const
{
return bytes(32 - _bytes.size(), 0) + std::move(_bytes);
}
bytes BytesUtils::applyAlign(
Parameter::Alignment _alignment,
ABIType& _abiType,
bytes _bytes
) const
{
if (_alignment != Parameter::Alignment::None)
_abiType.alignDeclared = true;
switch (_alignment)
{
case Parameter::Alignment::Left:
_abiType.align = ABIType::AlignLeft;
return alignLeft(std::move(_bytes));
case Parameter::Alignment::Right:
_abiType.align = ABIType::AlignRight;
return alignRight(std::move(_bytes));
default:
_abiType.align = ABIType::AlignRight;
return alignRight(std::move(_bytes));
}
}

View File

@ -0,0 +1,100 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <test/libsolidity/util/SoltestTypes.h>
#include <libdevcore/CommonData.h>
namespace dev
{
namespace solidity
{
namespace test
{
/**
* Utility class that aids conversions from parsed strings to an
* isoltest-internal, ABI-based bytes representation and vice-versa.
*/
class BytesUtils
{
public:
/// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the boolean number literal. Throws if conversion fails.
bytes convertBoolean(std::string const& _literal);
/// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the decimal number literal. Throws if conversion fails.
bytes convertNumber(std::string const& _literal);
/// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the hex literal. Throws if conversion fails.
bytes convertHexNumber(std::string const& _literal);
/// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the string literal. Throws if conversion fails.
bytes convertString(std::string const& _literal);
/// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold
/// an unsigned value.
std::string formatUnsigned(bytes const& _bytes) const;
/// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold
/// a signed value.
std::string formatSigned(bytes const& _bytes) const;
/// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold
/// a boolean value.
std::string formatBoolean(bytes const& _bytes) const;
/// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold
/// a hex value.
std::string formatHex(bytes const& _bytes) const;
/// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold
/// a hexString value.
std::string formatHexString(bytes const& _bytes) const;
/// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold
/// a string value.
std::string formatString(bytes const& _bytes) const;
/// Left-aligns and pads given _bytes and returns a new
/// bytes array.
bytes alignLeft(bytes _bytes) const;
/// Right-aligns and pads given _bytes and returns a new
/// bytes array.
bytes alignRight(bytes _bytes) const;
/// Applies given _alignment to _bytes and returns a new
/// bytes array.
bytes applyAlign(
Parameter::Alignment _alignment,
ABIType& _abiType,
bytes _bytes
) const;
};
}
}
}

View File

@ -0,0 +1,87 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <test/libsolidity/util/ContractABIUtils.h>
#include <liblangutil/Common.h>
#include <boost/algorithm/string.hpp>
#include <fstream>
#include <memory>
#include <regex>
#include <stdexcept>
using namespace dev;
using namespace langutil;
using namespace solidity;
using namespace dev::solidity::test;
using namespace std;
using namespace soltest;
dev::solidity::test::ParameterList ContractABIUtils::parametersFromJson(
Json::Value const& _contractABI,
string const& _functionName
) const
{
ParameterList abiParams;
for (auto const& function: _contractABI)
if (function["name"] == _functionName)
for (auto const& output: function["outputs"])
{
auto types = fromTypeName(output["type"].asString());
for (auto const& type: types)
abiParams.push_back(Parameter{bytes(), "", type, FormatInfo{}});
}
return abiParams;
}
std::vector<ABIType> ContractABIUtils::fromTypeName(string const& _type) const
{
static regex s_boolType{"(bool)"};
static regex s_uintType{"(uint\\d*)"};
static regex s_intType{"(int\\d*)"};
static regex s_bytesType{"(bytes\\d+)"};
static regex s_dynBytesType{"(\\bbytes\\b)"};
static regex s_stringType{"(string)"};
vector<ABIType> abiTypes;
if (regex_match(_type, s_boolType))
abiTypes.push_back(ABIType{ABIType::Boolean, ABIType::AlignRight, 32});
else if (regex_match(_type, s_uintType))
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
else if (regex_match(_type, s_intType))
abiTypes.push_back(ABIType{ABIType::SignedDec, ABIType::AlignRight, 32});
else if (regex_match(_type, s_bytesType))
abiTypes.push_back(ABIType{ABIType::Hex, ABIType::AlignRight, 32});
else if (regex_match(_type, s_dynBytesType))
{
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::HexString, ABIType::AlignLeft, 32});
}
else if (regex_match(_type, s_stringType))
{
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::String, ABIType::AlignLeft, 32});
}
else
abiTypes.push_back(ABIType{ABIType::None, ABIType::AlignRight, 0});
return abiTypes;
}

View File

@ -0,0 +1,59 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <test/libsolidity/util/SoltestTypes.h>
#include <libdevcore/CommonData.h>
#include <json/json.h>
namespace dev
{
namespace solidity
{
namespace test
{
/**
* Utility class that aids conversions from contract ABI types stored in a
* Json value to the internal ABIType representation of isoltest.
*/
class ContractABIUtils
{
public:
/// Parses and translates Solidity's ABI types as Json string into
/// a list of internal type representations of isoltest.
ParameterList parametersFromJson(
Json::Value const& _contractABI,
std::string const& _functionName
) const;
private:
/// Parses and translates a single type and returns a list of
/// internal type representations of isoltest.
/// Types defined by the ABI will translate to ABITypes
/// as follows:
/// `bool` -> [`Boolean`]
/// `uint` -> [`Unsigned`]
/// `string` -> [`Unsigned`, `Unsigned`, `String`]
/// `bytes` -> [`Unsigned`, `Unsigned`, `HexString`]
/// ...
std::vector<ABIType> fromTypeName(std::string const& _type) const;
};
}
}
}

View File

@ -0,0 +1,256 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libdevcore/CommonData.h>
#include <libsolidity/ast/Types.h>
namespace dev
{
namespace solidity
{
namespace test
{
/**
* All soltest tokens.
*/
#define SOLT_TOKEN_LIST(T, K) \
T(Unknown, "unknown", 0) \
T(Invalid, "invalid", 0) \
T(EOS, "EOS", 0) \
T(Whitespace, "_", 0) \
/* punctuations */ \
T(LParen, "(", 0) \
T(RParen, ")", 0) \
T(LBrack, "[", 0) \
T(RBrack, "]", 0) \
T(LBrace, "{", 0) \
T(RBrace, "}", 0) \
T(Sub, "-", 0) \
T(Colon, ":", 0) \
T(Comma, ",", 0) \
T(Period, ".", 0) \
T(Arrow, "->", 0) \
T(Newline, "//", 0) \
/* Literals & identifier */ \
T(Comment, "#", 0) \
T(Number, "number", 0) \
T(HexNumber, "hex_number", 0) \
T(String, "string", 0) \
T(Identifier, "identifier", 0) \
/* type keywords */ \
K(Ether, "ether", 0) \
K(Hex, "hex", 0) \
K(Boolean, "boolean", 0) \
/* special keywords */ \
K(Left, "left", 0) \
K(Right, "right", 0) \
K(Failure, "FAILURE", 0) \
namespace soltest
{
enum class Token : unsigned int {
#define T(name, string, precedence) name,
SOLT_TOKEN_LIST(T, T)
NUM_TOKENS
#undef T
};
/// Prints a friendly string representation of \param _token.
inline std::string formatToken(Token _token)
{
switch (_token)
{
#define T(name, string, precedence) case Token::name: return string;
SOLT_TOKEN_LIST(T, T)
#undef T
default: // Token::NUM_TOKENS:
return "";
}
}
}
/**
* The purpose of the ABI type is the storage of type information
* retrieved while parsing a test. This information is used
* for the conversion of human-readable function arguments and
* return values to `bytes` and vice-versa.
* Defaults to None, a 0-byte representation. 0-bytes
* can also be interpreted as Failure, which means
* either a REVERT or another EVM failure.
*/
struct ABIType
{
enum Type
{
None,
Failure,
Boolean,
UnsignedDec,
SignedDec,
Hex,
HexString,
String
};
enum Align
{
AlignLeft,
AlignRight,
AlignNone,
};
Type type = ABIType::None;
Align align = ABIType::AlignRight;
size_t size = 0;
bool alignDeclared = false;
};
/**
* Helper that can hold format information retrieved
* while scanning through a parameter list in soltest.
*/
struct FormatInfo
{
bool newline = false;
};
/**
* Parameter abstraction used for the encoding and decoding of
* function parameter and expectation / return value lists.
* A parameter list is usually a comma-separated list of literals.
* It should not be possible to call create a parameter holding
* an identifier, but if so, the ABI type would be invalid.
*/
struct Parameter
{
enum Alignment
{
Left,
Right,
None,
};
/// ABI encoded / decoded `bytes` of values.
/// These `bytes` are used to pass values to function calls
/// and also to store expected return vales. These are
/// compared to the actual result of a function call
/// and used for validating it.
bytes rawBytes;
/// Stores the raw string representation of this parameter.
/// Used to print the unformatted arguments of a function call.
std::string rawString;
/// Types that were used to encode `rawBytes`. Expectations
/// are usually comma separated literals. Their type is auto-
/// detected and retained in order to format them later on.
ABIType abiType;
/// Format info attached to the parameter. It handles newlines given
/// in the declaration of it.
FormatInfo format;
/// Stores the parsed alignment, which can be either left(...) or right(...).
Alignment alignment = Alignment::None;
};
using ParameterList = std::vector<Parameter>;
/**
* Represents the expected result of a function call after it has been executed. This may be a single
* return value or a comma-separated list of return values. It also contains the detected input
* formats used to convert the values to `bytes` needed for the comparison with the actual result
* of a call. In addition to that, it also stores the expected transaction status.
* An optional comment can be assigned.
*/
struct FunctionCallExpectations
{
/// Representation of the comma-separated (or empty) list of expected result values
/// attached to the function call object. It is checked against the actual result of
/// a function call when used in test framework.
ParameterList result;
/// Expected status of the transaction. It can be either
/// a REVERT or a different EVM failure (e.g. out-of-gas).
bool failure = true;
/// A Comment that can be attached to the expectations,
/// that is retained and can be displayed.
std::string comment;
/// ABI encoded `bytes` of parsed expected return values. It is checked
/// against the actual result of a function call when used in test framework.
bytes rawBytes() const
{
bytes raw;
for (auto const& param: result)
raw += param.rawBytes;
return raw;
}
};
/**
* Represents the arguments passed to a function call. This can be a single
* argument or a comma-separated list of arguments. It also contains the detected input
* formats used to convert the arguments to `bytes` needed for the call.
* An optional comment can be assigned.
*/
struct FunctionCallArgs
{
/// Types that were used to encode `rawBytes`. Parameters
/// are usually comma separated literals. Their type is auto-
/// detected and retained in order to format them later on.
ParameterList parameters;
/// A Comment that can be attached to the expectations,
/// that is retained and can be displayed.
std::string comment;
/// ABI encoded `bytes` of parsed parameters. These `bytes`
/// passed to the function call.
bytes rawBytes() const
{
bytes raw;
for (auto const& param: parameters)
raw += param.rawBytes;
return raw;
}
};
/**
* Represents a function call read from an input stream. It contains the signature, the
* arguments, an optional ether value and an expected execution result.
*/
struct FunctionCall
{
/// Signature of the function call, e.g. `f(uint256, uint256)`.
std::string signature;
/// Optional `ether` value that can be send with the call.
u256 value;
/// Object that holds all function parameters in their `bytes`
/// representations given by the contract ABI.
FunctionCallArgs arguments;
/// Object that holds all function call expectation in
/// their `bytes` representations given by the contract ABI.
/// They are checked against the actual results and their
/// `bytes` representation, as well as the transaction status.
FunctionCallExpectations expectations;
/// single / multi-line mode will be detected as follows:
/// every newline (//) in source results in a function call
/// that has its display mode set to multi-mode. Function and
/// result parameter lists are an exception: a single parameter
/// stores a format information that contains a newline definition.
enum DisplayMode {
SingleLine,
MultiLine
};
DisplayMode displayMode = DisplayMode::SingleLine;
/// Marks this function call as the constructor.
bool isConstructor = false;
};
}
}
}

View File

@ -16,12 +16,17 @@
*/ */
#include <test/libsolidity/util/TestFileParser.h> #include <test/libsolidity/util/TestFileParser.h>
#include <test/libsolidity/util/BytesUtils.h>
#include <test/Options.h> #include <test/Options.h>
#include <liblangutil/Common.h> #include <liblangutil/Common.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/throw_exception.hpp> #include <boost/throw_exception.hpp>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
@ -33,45 +38,6 @@ using namespace dev::solidity::test;
using namespace std; using namespace std;
using namespace soltest; using namespace soltest;
namespace
{
enum class DeclaredAlignment
{
Left,
Right,
None,
};
inline bytes alignLeft(bytes _bytes)
{
return std::move(_bytes) + bytes(32 - _bytes.size(), 0);
}
inline bytes alignRight(bytes _bytes)
{
return bytes(32 - _bytes.size(), 0) + std::move(_bytes);
}
inline bytes applyAlign(DeclaredAlignment _alignment, ABIType& _abiType, bytes _converted)
{
if (_alignment != DeclaredAlignment::None)
_abiType.alignDeclared = true;
switch (_alignment)
{
case DeclaredAlignment::Left:
_abiType.align = ABIType::AlignLeft;
return alignLeft(std::move(_converted));
case DeclaredAlignment::Right:
_abiType.align = ABIType::AlignRight;
return alignRight(std::move(_converted));
default:
_abiType.align = ABIType::AlignRight;
return alignRight(std::move(_converted));
}
}
}
char TestFileParser::Scanner::peek() const noexcept char TestFileParser::Scanner::peek() const noexcept
{ {
if (std::distance(m_char, m_line.end()) < 2) if (std::distance(m_char, m_line.end()) < 2)
@ -242,111 +208,125 @@ Parameter TestFileParser::parseParameter()
Parameter parameter; Parameter parameter;
if (accept(Token::Newline, true)) if (accept(Token::Newline, true))
parameter.format.newline = true; parameter.format.newline = true;
auto literal = parseABITypeLiteral();
parameter.rawBytes = get<0>(literal);
parameter.abiType = get<1>(literal);
parameter.rawString = get<2>(literal);
return parameter;
}
tuple<bytes, ABIType, string> TestFileParser::parseABITypeLiteral()
{
ABIType abiType{ABIType::None, ABIType::AlignNone, 0};
DeclaredAlignment alignment{DeclaredAlignment::None};
bytes result{toBigEndian(u256{0})};
string rawString;
bool isSigned = false; bool isSigned = false;
if (accept(Token::Left, true)) if (accept(Token::Left, true))
{ {
rawString += formatToken(Token::Left); parameter.rawString += formatToken(Token::Left);
expect(Token::LParen); expect(Token::LParen);
rawString += formatToken(Token::LParen); parameter.rawString += formatToken(Token::LParen);
alignment = DeclaredAlignment::Left; parameter.alignment = Parameter::Alignment::Left;
} }
if (accept(Token::Right, true)) if (accept(Token::Right, true))
{ {
rawString += formatToken(Token::Right); parameter.rawString += formatToken(Token::Right);
expect(Token::LParen); expect(Token::LParen);
rawString += formatToken(Token::LParen); parameter.rawString += formatToken(Token::LParen);
alignment = DeclaredAlignment::Right; parameter.alignment = Parameter::Alignment::Right;
} }
try try
{ {
if (accept(Token::Sub, true)) if (accept(Token::Sub, true))
{ {
rawString += formatToken(Token::Sub); parameter.rawString += formatToken(Token::Sub);
isSigned = true; isSigned = true;
} }
if (accept(Token::Boolean)) if (accept(Token::Boolean))
{ {
if (isSigned) if (isSigned)
throw Error(Error::Type::ParserError, "Invalid boolean literal."); throw Error(Error::Type::ParserError, "Invalid boolean literal.");
abiType = ABIType{ABIType::Boolean, ABIType::AlignRight, 32};
parameter.abiType = ABIType{ABIType::Boolean, ABIType::AlignRight, 32};
string parsed = parseBoolean(); string parsed = parseBoolean();
rawString += parsed; parameter.rawString += parsed;
result = applyAlign(alignment, abiType, convertBoolean(parsed)); parameter.rawBytes = BytesUtils().applyAlign(
parameter.alignment,
parameter.abiType,
BytesUtils().convertBoolean(parsed)
);
} }
else if (accept(Token::HexNumber)) else if (accept(Token::HexNumber))
{ {
if (isSigned) if (isSigned)
throw Error(Error::Type::ParserError, "Invalid hex number literal."); throw Error(Error::Type::ParserError, "Invalid hex number literal.");
abiType = ABIType{ABIType::Hex, ABIType::AlignRight, 32};
parameter.abiType = ABIType{ABIType::Hex, ABIType::AlignRight, 32};
string parsed = parseHexNumber(); string parsed = parseHexNumber();
rawString += parsed; parameter.rawString += parsed;
result = applyAlign(alignment, abiType, convertHexNumber(parsed)); parameter.rawBytes = BytesUtils().applyAlign(
parameter.alignment,
parameter.abiType,
BytesUtils().convertHexNumber(parsed)
);
} }
else if (accept(Token::Hex, true)) else if (accept(Token::Hex, true))
{ {
if (isSigned) if (isSigned)
throw Error(Error::Type::ParserError, "Invalid hex string literal."); throw Error(Error::Type::ParserError, "Invalid hex string literal.");
if (alignment != DeclaredAlignment::None) if (parameter.alignment != Parameter::Alignment::None)
throw Error(Error::Type::ParserError, "Hex string literals cannot be aligned or padded."); throw Error(Error::Type::ParserError, "Hex string literals cannot be aligned or padded.");
string parsed = parseString(); string parsed = parseString();
rawString += "hex\"" + parsed + "\""; parameter.rawString += "hex\"" + parsed + "\"";
result = convertHexNumber(parsed); parameter.rawBytes = BytesUtils().convertHexNumber(parsed);
abiType = ABIType{ABIType::HexString, ABIType::AlignNone, result.size()}; parameter.abiType = ABIType{
ABIType::HexString, ABIType::AlignNone, parameter.rawBytes.size()
};
} }
else if (accept(Token::String)) else if (accept(Token::String))
{ {
if (isSigned) if (isSigned)
throw Error(Error::Type::ParserError, "Invalid string literal."); throw Error(Error::Type::ParserError, "Invalid string literal.");
if (alignment != DeclaredAlignment::None) if (parameter.alignment != Parameter::Alignment::None)
throw Error(Error::Type::ParserError, "String literals cannot be aligned or padded."); throw Error(Error::Type::ParserError, "String literals cannot be aligned or padded.");
abiType = ABIType{ABIType::String, ABIType::AlignLeft, 32};
parameter.abiType = ABIType{ABIType::String, ABIType::AlignLeft, 32};
string parsed = parseString(); string parsed = parseString();
rawString += "\"" + parsed + "\""; parameter.rawString += "\"" + parsed + "\"";
result = applyAlign(DeclaredAlignment::Left, abiType, convertString(parsed)); parameter.rawBytes = BytesUtils().applyAlign(
Parameter::Alignment::Left,
parameter.abiType,
BytesUtils().convertString(parsed)
);
} }
else if (accept(Token::Number)) else if (accept(Token::Number))
{ {
auto type = isSigned ? ABIType::SignedDec : ABIType::UnsignedDec; auto type = isSigned ? ABIType::SignedDec : ABIType::UnsignedDec;
abiType = ABIType{type, ABIType::AlignRight, 32};
parameter.abiType = ABIType{type, ABIType::AlignRight, 32};
string parsed = parseDecimalNumber(); string parsed = parseDecimalNumber();
rawString += parsed; parameter.rawString += parsed;
if (isSigned) if (isSigned)
parsed = "-" + parsed; parsed = "-" + parsed;
result = applyAlign(alignment, abiType, convertNumber(parsed));
parameter.rawBytes = BytesUtils().applyAlign(
parameter.alignment,
parameter.abiType,
BytesUtils().convertNumber(parsed)
);
} }
else if (accept(Token::Failure, true)) else if (accept(Token::Failure, true))
{ {
if (isSigned) if (isSigned)
throw Error(Error::Type::ParserError, "Invalid failure literal."); throw Error(Error::Type::ParserError, "Invalid failure literal.");
abiType = ABIType{ABIType::Failure, ABIType::AlignRight, 0};
result = bytes{}; parameter.abiType = ABIType{ABIType::Failure, ABIType::AlignRight, 0};
parameter.rawBytes = bytes{};
} }
if (alignment != DeclaredAlignment::None) if (parameter.alignment != Parameter::Alignment::None)
{ {
expect(Token::RParen); expect(Token::RParen);
rawString += formatToken(Token::RParen); parameter.rawString += formatToken(Token::RParen);
} }
return make_tuple(result, abiType, rawString);
} }
catch (std::exception const&) catch (std::exception const&)
{ {
throw Error(Error::Type::ParserError, "Literal encoding invalid."); throw Error(Error::Type::ParserError, "Literal encoding invalid.");
} }
return parameter;
} }
string TestFileParser::parseIdentifierOrTuple() string TestFileParser::parseIdentifierOrTuple()
@ -426,55 +406,6 @@ string TestFileParser::parseString()
return literal; return literal;
} }
bytes TestFileParser::convertBoolean(string const& _literal)
{
if (_literal == "true")
return bytes{true};
else if (_literal == "false")
return bytes{false};
else
throw Error(Error::Type::ParserError, "Boolean literal invalid.");
}
bytes TestFileParser::convertNumber(string const& _literal)
{
try
{
return toCompactBigEndian(u256{_literal});
}
catch (std::exception const&)
{
throw Error(Error::Type::ParserError, "Number encoding invalid.");
}
}
bytes TestFileParser::convertHexNumber(string const& _literal)
{
try
{
if (_literal.size() % 2)
throw Error(Error::Type::ParserError, "Hex number encoding invalid.");
else
return fromHex(_literal);
}
catch (std::exception const&)
{
throw Error(Error::Type::ParserError, "Hex number encoding invalid.");
}
}
bytes TestFileParser::convertString(string const& _literal)
{
try
{
return asBytes(_literal);
}
catch (std::exception const&)
{
throw Error(Error::Type::ParserError, "String encoding invalid.");
}
}
void TestFileParser::Scanner::readStream(istream& _stream) void TestFileParser::Scanner::readStream(istream& _stream)
{ {
std::string line; std::string line;

View File

@ -17,6 +17,7 @@
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
#include <libsolidity/ast/Types.h> #include <libsolidity/ast/Types.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <test/libsolidity/util/SoltestTypes.h>
#include <iosfwd> #include <iosfwd>
#include <iterator> #include <iterator>
@ -33,226 +34,6 @@ namespace solidity
namespace test namespace test
{ {
/**
* All soltest tokens.
*/
#define SOLT_TOKEN_LIST(T, K) \
T(Unknown, "unknown", 0) \
T(Invalid, "invalid", 0) \
T(EOS, "EOS", 0) \
T(Whitespace, "_", 0) \
/* punctuations */ \
T(LParen, "(", 0) \
T(RParen, ")", 0) \
T(LBrack, "[", 0) \
T(RBrack, "]", 0) \
T(LBrace, "{", 0) \
T(RBrace, "}", 0) \
T(Sub, "-", 0) \
T(Colon, ":", 0) \
T(Comma, ",", 0) \
T(Period, ".", 0) \
T(Arrow, "->", 0) \
T(Newline, "//", 0) \
/* Literals & identifier */ \
T(Comment, "#", 0) \
T(Number, "number", 0) \
T(HexNumber, "hex_number", 0) \
T(String, "string", 0) \
T(Identifier, "identifier", 0) \
/* type keywords */ \
K(Ether, "ether", 0) \
K(Hex, "hex", 0) \
K(Boolean, "boolean", 0) \
/* special keywords */ \
K(Left, "left", 0) \
K(Right, "right", 0) \
K(Failure, "FAILURE", 0) \
namespace soltest
{
enum class Token : unsigned int {
#define T(name, string, precedence) name,
SOLT_TOKEN_LIST(T, T)
NUM_TOKENS
#undef T
};
/// Prints a friendly string representation of \param _token.
inline std::string formatToken(Token _token)
{
switch (_token)
{
#define T(name, string, precedence) case Token::name: return string;
SOLT_TOKEN_LIST(T, T)
#undef T
default: // Token::NUM_TOKENS:
return "";
}
}
}
/**
* The purpose of the ABI type is the storage of type information
* retrieved while parsing a test. This information is used
* for the conversion of human-readable function arguments and
* return values to `bytes` and vice-versa.
* Defaults to None, a 0-byte representation. 0-bytes
* can also be interpreted as Failure, which means
* either a REVERT or another EVM failure.
*/
struct ABIType
{
enum Type
{
None,
Failure,
Boolean,
UnsignedDec,
SignedDec,
Hex,
HexString,
String
};
enum Align
{
AlignLeft,
AlignRight,
AlignNone,
};
Type type = ABIType::None;
Align align = ABIType::AlignRight;
size_t size = 0;
bool alignDeclared = false;
};
/**
* Helper that can hold format information retrieved
* while scanning through a parameter list in soltest.
*/
struct FormatInfo
{
bool newline = false;
};
/**
* Parameter abstraction used for the encoding and decoding of
* function parameter and expectation / return value lists.
* A parameter list is usually a comma-separated list of literals.
* It should not be possible to call create a parameter holding
* an identifier, but if so, the ABI type would be invalid.
*/
struct Parameter
{
/// ABI encoded / decoded `bytes` of values.
/// These `bytes` are used to pass values to function calls
/// and also to store expected return vales. These are
/// compared to the actual result of a function call
/// and used for validating it.
bytes rawBytes;
/// Stores the raw string representation of this parameter.
/// Used to print the unformatted arguments of a function call.
std::string rawString;
/// Types that were used to encode `rawBytes`. Expectations
/// are usually comma separated literals. Their type is auto-
/// detected and retained in order to format them later on.
ABIType abiType;
/// Format info attached to the parameter. It handles newlines given
/// in the declaration of it.
FormatInfo format;
};
using ParameterList = std::vector<Parameter>;
/**
* Represents the expected result of a function call after it has been executed. This may be a single
* return value or a comma-separated list of return values. It also contains the detected input
* formats used to convert the values to `bytes` needed for the comparison with the actual result
* of a call. In addition to that, it also stores the expected transaction status.
* An optional comment can be assigned.
*/
struct FunctionCallExpectations
{
/// Representation of the comma-separated (or empty) list of expected result values
/// attached to the function call object. It is checked against the actual result of
/// a function call when used in test framework.
ParameterList result;
/// Expected status of the transaction. It can be either
/// a REVERT or a different EVM failure (e.g. out-of-gas).
bool failure = true;
/// A Comment that can be attached to the expectations,
/// that is retained and can be displayed.
std::string comment;
/// ABI encoded `bytes` of parsed expected return values. It is checked
/// against the actual result of a function call when used in test framework.
bytes rawBytes() const
{
bytes raw;
for (auto const& param: result)
raw += param.rawBytes;
return raw;
}
};
/**
* Represents the arguments passed to a function call. This can be a single
* argument or a comma-separated list of arguments. It also contains the detected input
* formats used to convert the arguments to `bytes` needed for the call.
* An optional comment can be assigned.
*/
struct FunctionCallArgs
{
/// Types that were used to encode `rawBytes`. Parameters
/// are usually comma separated literals. Their type is auto-
/// detected and retained in order to format them later on.
ParameterList parameters;
/// A Comment that can be attached to the expectations,
/// that is retained and can be displayed.
std::string comment;
/// ABI encoded `bytes` of parsed parameters. These `bytes`
/// passed to the function call.
bytes rawBytes() const
{
bytes raw;
for (auto const& param: parameters)
raw += param.rawBytes;
return raw;
}
};
/**
* Represents a function call read from an input stream. It contains the signature, the
* arguments, an optional ether value and an expected execution result.
*/
struct FunctionCall
{
/// Signature of the function call, e.g. `f(uint256, uint256)`.
std::string signature;
/// Optional `ether` value that can be send with the call.
u256 value;
/// Object that holds all function parameters in their `bytes`
/// representations given by the contract ABI.
FunctionCallArgs arguments;
/// Object that holds all function call expectation in
/// their `bytes` representations given by the contract ABI.
/// They are checked against the actual results and their
/// `bytes` representation, as well as the transaction status.
FunctionCallExpectations expectations;
/// single / multi-line mode will be detected as follows:
/// every newline (//) in source results in a function call
/// that has its display mode set to multi-mode. Function and
/// result parameter lists are an exception: a single parameter
/// stores a format information that contains a newline definition.
enum DisplayMode {
SingleLine,
MultiLine
};
DisplayMode displayMode = DisplayMode::SingleLine;
/// Marks this function call as the constructor.
bool isConstructor = false;
};
/** /**
* Class that is able to parse an additional and well-formed comment section in a Solidity * Class that is able to parse an additional and well-formed comment section in a Solidity
* source file used by the file-based unit test environment. For now, it parses function * source file used by the file-based unit test environment. For now, it parses function
@ -365,9 +146,7 @@ private:
/// appends it to the internal `bytes` buffer of the parameter. It can also /// appends it to the internal `bytes` buffer of the parameter. It can also
/// store newlines found in the source, that are needed to /// store newlines found in the source, that are needed to
/// format input and output of the interactive update. /// format input and output of the interactive update.
Parameter parseParameter(); /// Parses and converts the current literal to its byte representation and
/// Parses and converts the current literal to its byte representation and
/// preserves the chosen ABI type, as well as a raw, unformatted string representation /// preserves the chosen ABI type, as well as a raw, unformatted string representation
/// of this literal. /// of this literal.
/// Based on the type information retrieved, the driver of this parser may format arguments, /// Based on the type information retrieved, the driver of this parser may format arguments,
@ -376,7 +155,7 @@ private:
/// Returns invalid ABI type for empty literal. This is needed in order /// Returns invalid ABI type for empty literal. This is needed in order
/// to detect empty expectations. Throws a ParserError if data is encoded incorrectly or /// to detect empty expectations. Throws a ParserError if data is encoded incorrectly or
/// if data type is not supported. /// if data type is not supported.
std::tuple<bytes, ABIType, std::string> parseABITypeLiteral(); Parameter parseParameter();
/// Recursively parses an identifier or a tuple definition that contains identifiers /// Recursively parses an identifier or a tuple definition that contains identifiers
/// and / or parentheses like `((uint, uint), (uint, (uint, uint)), uint)`. /// and / or parentheses like `((uint, uint), (uint, (uint, uint)), uint)`.
@ -398,22 +177,6 @@ private:
/// Parses the current string literal. /// Parses the current string literal.
std::string parseString(); std::string parseString();
/// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the boolean number literal. Throws if conversion fails.
bytes convertBoolean(std::string const& _literal);
/// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the decimal number literal. Throws if conversion fails.
bytes convertNumber(std::string const& _literal);
/// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the hex literal. Throws if conversion fails.
bytes convertHexNumber(std::string const& _literal);
/// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the string literal. Throws if conversion fails.
bytes convertString(std::string const& _literal);
/// A scanner instance /// A scanner instance
Scanner m_scanner; Scanner m_scanner;
}; };

View File

@ -14,11 +14,13 @@
#include <test/libsolidity/util/TestFunctionCall.h> #include <test/libsolidity/util/TestFunctionCall.h>
#include <test/libsolidity/util/BytesUtils.h>
#include <test/libsolidity/util/ContractABIUtils.h>
#include <libdevcore/AnsiColorized.h> #include <libdevcore/AnsiColorized.h>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
#include <regex>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@ -27,47 +29,6 @@ using namespace solidity;
using namespace dev::solidity::test; using namespace dev::solidity::test;
using namespace std; using namespace std;
namespace
{
static regex s_boolType{"(bool)"};
static regex s_uintType{"(uint\\d*)"};
static regex s_intType{"(int\\d*)"};
static regex s_bytesType{"(bytes\\d+)"};
static regex s_dynBytesType{"(\\bbytes\\b)"};
static regex s_stringType{"(string)"};
/// Translates Solidity's ABI types into the internal type representation of
/// soltest.
auto contractABITypes(string const& _type) -> vector<ABIType>
{
vector<ABIType> abiTypes;
if (regex_match(_type, s_boolType))
abiTypes.push_back(ABIType{ABIType::Boolean, ABIType::AlignRight, 32});
else if (regex_match(_type, s_uintType))
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
else if (regex_match(_type, s_intType))
abiTypes.push_back(ABIType{ABIType::SignedDec, ABIType::AlignRight, 32});
else if (regex_match(_type, s_bytesType))
abiTypes.push_back(ABIType{ABIType::Hex, ABIType::AlignRight, 32});
else if (regex_match(_type, s_dynBytesType))
{
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::HexString, ABIType::AlignLeft, 32});
}
else if (regex_match(_type, s_stringType))
{
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::String, ABIType::AlignLeft, 32});
}
else
abiTypes.push_back(ABIType{ABIType::None, ABIType::AlignRight, 0});
return abiTypes;
};
}
string TestFunctionCall::format( string TestFunctionCall::format(
ErrorReporter& _errorReporter, ErrorReporter& _errorReporter,
string const& _linePrefix, string const& _linePrefix,
@ -192,21 +153,9 @@ string TestFunctionCall::formatBytesParameters(
stringstream os; stringstream os;
string functionName{_signature.substr(0, _signature.find("("))}; string functionName{_signature.substr(0, _signature.find("("))};
auto sizeFold = [](size_t const _a, Parameter const& _b) { return _a + _b.abiType.size; }; /// Create parameters from Contract ABI. Used to generate values for
size_t encodingSize = std::accumulate(_params.begin(), _params.end(), size_t{0}, sizeFold);
/// Infer type from Contract ABI. Used to generate values for
/// auto-correction during interactive update routine. /// auto-correction during interactive update routine.
ParameterList abiParams; ParameterList abiParams = ContractABIUtils().parametersFromJson(m_contractABI, functionName);
for (auto const& function: m_contractABI)
if (function["name"] == functionName)
for (auto const& output: function["outputs"])
{
auto types = contractABITypes(output["type"].asString());
for (auto const& type: types)
abiParams.push_back(Parameter{bytes(), "", type, FormatInfo{}});
}
/// If parameter count does not match, take types defined by ABI, but only /// If parameter count does not match, take types defined by ABI, but only
/// if the contract ABI is defined (needed for format tests where the actual /// if the contract ABI is defined (needed for format tests where the actual
@ -214,6 +163,9 @@ string TestFunctionCall::formatBytesParameters(
ParameterList preferredParams; ParameterList preferredParams;
if (m_contractABI && (_params.size() != abiParams.size())) if (m_contractABI && (_params.size() != abiParams.size()))
{ {
auto sizeFold = [](size_t const _a, Parameter const& _b) { return _a + _b.abiType.size; };
size_t encodingSize = std::accumulate(_params.begin(), _params.end(), size_t{0}, sizeFold);
_errorReporter.warning( _errorReporter.warning(
"Encoding does not match byte range. The call returned " + "Encoding does not match byte range. The call returned " +
to_string(_bytes.size()) + " bytes, but " + to_string(_bytes.size()) + " bytes, but " +
@ -294,57 +246,23 @@ string TestFunctionCall::formatBytesRange(
// be signed. If an unsigned was detected in the expectations, // be signed. If an unsigned was detected in the expectations,
// but the actual result returned a signed, it would be formatted // but the actual result returned a signed, it would be formatted
// incorrectly. // incorrectly.
if (*_bytes.begin() & 0x80) os << BytesUtils().formatUnsigned(_bytes);
os << u2s(fromBigEndian<u256>(_bytes));
else
os << fromBigEndian<u256>(_bytes);
break; break;
case ABIType::SignedDec: case ABIType::SignedDec:
if (*_bytes.begin() & 0x80) os << BytesUtils().formatSigned(_bytes);
os << u2s(fromBigEndian<u256>(_bytes));
else
os << fromBigEndian<u256>(_bytes);
break; break;
case ABIType::Boolean: case ABIType::Boolean:
{ os << BytesUtils().formatBoolean(_bytes);
u256 result = fromBigEndian<u256>(_bytes);
if (result == 0)
os << "false";
else if (result == 1)
os << "true";
else
os << result;
break; break;
}
case ABIType::Hex: case ABIType::Hex:
{ os << BytesUtils().formatHex(_bytes);
string hex{toHex(_bytes, HexPrefix::Add)};
boost::algorithm::replace_all(hex, "00", "");
os << hex;
break; break;
}
case ABIType::HexString: case ABIType::HexString:
os << "hex\"" << toHex(_bytes) << "\""; os << BytesUtils().formatHexString(_bytes);
break; break;
case ABIType::String: case ABIType::String:
{ os << BytesUtils().formatString(_bytes);
os << "\"";
bool expectZeros = false;
for (auto const& v: _bytes)
{
if (expectZeros && v != 0)
return {};
if (v == 0) expectZeros = true;
else
{
if (!isprint(v) || v == '"')
return {};
os << v;
}
}
os << "\"";
break; break;
}
case ABIType::Failure: case ABIType::Failure:
break; break;
case ABIType::None: case ABIType::None:

View File

@ -16,6 +16,8 @@ add_executable(isoltest
../Options.cpp ../Options.cpp
../Common.cpp ../Common.cpp
../TestCase.cpp ../TestCase.cpp
../libsolidity/util/BytesUtils.cpp
../libsolidity/util/ContractABIUtils.cpp
../libsolidity/util/TestFileParser.cpp ../libsolidity/util/TestFileParser.cpp
../libsolidity/util/TestFunctionCall.cpp ../libsolidity/util/TestFunctionCall.cpp
../libsolidity/GasTest.cpp ../libsolidity/GasTest.cpp