Merge pull request #7052 from ethereum/isoltest-update-improvements

[isoltest] Update improvements (support for missing types)
This commit is contained in:
chriseth 2019-08-05 16:45:06 +02:00 committed by GitHub
commit 42290f912b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 728 additions and 343 deletions

View File

@ -35,14 +35,6 @@ namespace solidity
namespace test namespace test
{ {
#define soltestAssert(CONDITION, DESCRIPTION) \
do \
{ \
if (!(CONDITION)) \
BOOST_THROW_EXCEPTION(runtime_error(DESCRIPTION)); \
} \
while (false)
/** /**
* Common superclass of anything that can be run via isoltest. * Common superclass of anything that can be run via isoltest.
*/ */

View File

@ -1,5 +1,17 @@
pragma experimental ABIEncoderV2;
contract C { contract C {
struct S {
uint a;
uint b;
}
struct T {
uint a;
uint b;
string s;
}
uint public state = 0; uint public state = 0;
bool[2][] flags;
constructor(uint _state) public payable { constructor(uint _state) public payable {
state = _state; state = _state;
} }
@ -12,6 +24,9 @@ contract C {
function f() payable public returns (uint) { function f() payable public returns (uint) {
return 2; return 2;
} }
function f(uint a) public returns (uint, uint) {
return (a, a);
}
function g() public returns (uint, uint) { function g() public returns (uint, uint) {
return (2, 3); return (2, 3);
} }
@ -42,6 +57,36 @@ contract C {
function q(uint a) public returns (uint d) { function q(uint a) public returns (uint d) {
return a * 7; return a * 7;
} }
function r() public returns (bool[3] memory) {
return [true, false, true];
}
function s() public returns (uint[2] memory, uint) {
return ([uint(123), 456], 789);
}
function t1() public returns (S memory) {
return S(23, 42);
}
function t2() public returns (T memory) {
return T(23, 42, "any");
}
function u() public returns (T[2] memory) {
return [T(23, 42, "any"), T(555, 666, "any")];
}
function v() public returns (bool[2][] memory) {
return flags;
}
function w1() public returns (string[1] memory) {
return ["any"];
}
function w2() public returns (string[2] memory) {
return ["any", "any"];
}
function w3() public returns (string[3] memory) {
return ["any", "any", "any"];
}
function x() public returns (string[2] memory, string[3] memory) {
return (["any", "any"], ["any", "any", "any"]);
}
} }
// ---- // ----
// constructor(), 2 ether: 3 -> // constructor(), 2 ether: 3 ->
@ -50,17 +95,27 @@ contract C {
// _() -> FAILURE // _() -> FAILURE
// e(uint256): 4 // e(uint256): 4
// f() -> 2 // f() -> 2
// f(uint256): 3 -> 3, 3
// f(), 1 ether -> 2 // f(), 1 ether -> 2
// g() -> 2, 3 // g() -> 2, 3
// g1() -> FAILURE
// h(uint256,uint256): 1, -2 -> 3 // h(uint256,uint256): 1, -2 -> 3
// j(bool): true -> false // j(bool): true -> false
// k(bytes32): 0x10 -> 0x10, 0x10 // k(bytes32): 0x10 -> 0x10, 0x10
// l(): hex"4200ef" -> 7 // l(): hex"4200efef" -> 8
// m(bytes): 32, 32, 0x20 -> 32, 32, 0x20 // m(bytes): 32, 32, 0x20 -> 32, 32, 0x20
// m(bytes): 32, 3, hex"AB33BB" -> 32, 3, left(0xAB33BB) // m(bytes): 32, 3, hex"AB33BB" -> 32, 3, left(0xAB33BB)
// m(bytes): 32, 3, hex"AB33FF" -> 32, 3, hex"ab33ff0000000000000000000000000000000000000000000000000000000000" // m(bytes): 32, 3, hex"AB33FF" -> 32, 3, hex"ab33ff0000000000000000000000000000000000000000000000000000000000"
// n() -> 0x20, 3, "any" // n() -> 0x20, 3, "any"
// o() -> 0x40, 0x80, 3, "any", 3, "any" // o() -> 0x40, 0x80, 3, "any", 3, "any"
// p() -> 0x60, 0x2a, 0xa0, 3, "any", 3, "any" // p() -> 0x60, 0x2a, 0xa0, 3, "any", 3, "any"
// q(uint256): 0 -> 0
// q(uint256): 99 -> 693 // q(uint256): 99 -> 693
// r() -> true, false, true
// s() -> 123, 456, 789
// t1() -> 23, 42
// t2() -> 0x20, 23, 42, 0x60, 3, "any"
// v() -> 32, 0
// w1() -> 0x20, 0x20, 3, "any"
// w2() -> 0x20, 0x40, 0x80, 3, "any", 3, "any"
// w3() -> 0x20, 0x60, 0xa0, 0xe0, 3, "any", 3, "any", 3, "any"
// x() -> 0x40, 0x0100, 0x40, 0x80, 3, "any", 3, "any", 0x60, 0xa0, 0xe0, 3, "any", 3, "any", 3, "any"

View File

@ -17,6 +17,8 @@
#include <test/libsolidity/util/BytesUtils.h> #include <test/libsolidity/util/BytesUtils.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <liblangutil/Common.h> #include <liblangutil/Common.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@ -34,6 +36,40 @@ using namespace dev::solidity::test;
using namespace std; using namespace std;
using namespace soltest; using namespace soltest;
bytes BytesUtils::alignLeft(bytes _bytes)
{
soltestAssert(_bytes.size() <= 32, "");
size_t size = _bytes.size();
return std::move(_bytes) + bytes(32 - size, 0);
}
bytes BytesUtils::alignRight(bytes _bytes)
{
soltestAssert(_bytes.size() <= 32, "");
return bytes(32 - _bytes.size(), 0) + std::move(_bytes);
}
bytes BytesUtils::applyAlign(
Parameter::Alignment _alignment,
ABIType& _abiType,
bytes _bytes
)
{
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:
default:
_abiType.align = ABIType::AlignRight;
return alignRight(std::move(_bytes));
}
}
bytes BytesUtils::convertBoolean(string const& _literal) bytes BytesUtils::convertBoolean(string const& _literal)
{ {
if (_literal == "true") if (_literal == "true")
@ -83,10 +119,21 @@ bytes BytesUtils::convertString(string const& _literal)
} }
} }
string BytesUtils::formatUnsigned(bytes const& _bytes) const string BytesUtils::formatUnsigned(bytes const& _bytes)
{ {
stringstream os; stringstream os;
soltestAssert(!_bytes.empty() && _bytes.size() <= 32, "");
return fromBigEndian<u256>(_bytes).str();
}
string BytesUtils::formatSigned(bytes const& _bytes)
{
stringstream os;
soltestAssert(!_bytes.empty() && _bytes.size() <= 32, "");
if (*_bytes.begin() & 0x80) if (*_bytes.begin() & 0x80)
os << u2s(fromBigEndian<u256>(_bytes)); os << u2s(fromBigEndian<u256>(_bytes));
else else
@ -95,19 +142,7 @@ string BytesUtils::formatUnsigned(bytes const& _bytes) const
return os.str(); return os.str();
} }
string BytesUtils::formatSigned(bytes const& _bytes) const string BytesUtils::formatBoolean(bytes const& _bytes)
{
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; stringstream os;
u256 result = fromBigEndian<u256>(_bytes); u256 result = fromBigEndian<u256>(_bytes);
@ -122,7 +157,7 @@ string BytesUtils::formatBoolean(bytes const& _bytes) const
return os.str(); return os.str();
} }
string BytesUtils::formatHex(bytes const& _bytes) const string BytesUtils::formatHex(bytes const& _bytes)
{ {
stringstream os; stringstream os;
@ -133,7 +168,7 @@ string BytesUtils::formatHex(bytes const& _bytes) const
return os.str(); return os.str();
} }
string BytesUtils::formatHexString(bytes const& _bytes) const string BytesUtils::formatHexString(bytes const& _bytes)
{ {
stringstream os; stringstream os;
@ -142,7 +177,7 @@ string BytesUtils::formatHexString(bytes const& _bytes) const
return os.str(); return os.str();
} }
string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff) const string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff)
{ {
stringstream os; stringstream os;
@ -171,35 +206,96 @@ string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff) const
return os.str(); return os.str();
} }
bytes BytesUtils::alignLeft(bytes _bytes) const string BytesUtils::formatRawBytes(bytes const& _bytes)
{ {
return std::move(_bytes) + bytes(32 - _bytes.size(), 0); if (_bytes.empty())
} return "[]";
bytes BytesUtils::alignRight(bytes _bytes) const stringstream os;
{ auto it = _bytes.begin();
return bytes(32 - _bytes.size(), 0) + std::move(_bytes); for (size_t i = 0; i < _bytes.size(); i += 32)
}
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: bytes byteRange{it, it + 32};
_abiType.align = ABIType::AlignLeft;
return alignLeft(std::move(_bytes)); os << " " << byteRange;
case Parameter::Alignment::Right:
_abiType.align = ABIType::AlignRight; it += 32;
return alignRight(std::move(_bytes)); if (it != _bytes.end())
default: os << endl;
_abiType.align = ABIType::AlignRight;
return alignRight(std::move(_bytes));
} }
return os.str();
} }
string BytesUtils::formatBytes(
bytes const& _bytes,
ABIType const& _abiType
)
{
stringstream os;
switch (_abiType.type)
{
case ABIType::UnsignedDec:
// Check if the detected type was wrong and if this could
// be signed. If an unsigned was detected in the expectations,
// but the actual result returned a signed, it would be formatted
// incorrectly.
if (*_bytes.begin() & 0x80)
os << formatSigned(_bytes);
else
os << formatUnsigned(_bytes);
break;
case ABIType::SignedDec:
os << formatSigned(_bytes);
break;
case ABIType::Boolean:
os << formatBoolean(_bytes);
break;
case ABIType::Hex:
os << formatHex(_bytes);
break;
case ABIType::HexString:
os << formatHexString(_bytes);
break;
case ABIType::String:
os << formatString(_bytes);
break;
case ABIType::Failure:
break;
case ABIType::None:
break;
}
return os.str();
}
string BytesUtils::formatBytesRange(
bytes _bytes,
dev::solidity::test::ParameterList const& _parameters,
bool _highlight
)
{
stringstream os;
auto it = _bytes.begin();
for (auto const& parameter: _parameters)
{
bytes byteRange{it, it + static_cast<long>(parameter.abiType.size)};
if (!parameter.matchesBytes(byteRange))
AnsiColorized(
os,
_highlight,
{dev::formatting::RED_BACKGROUND}
) << formatBytes(byteRange, parameter.abiType);
else
os << parameter.rawString;
it += static_cast<long>(parameter.abiType.size);
if (&parameter != &_parameters.back())
os << ", ";
}
return os.str();
}

View File

@ -32,72 +32,92 @@ namespace test
class BytesUtils class BytesUtils
{ {
public: public:
/// Left-aligns and pads given _bytes and returns a new
/// bytes array.
static bytes alignLeft(bytes _bytes);
/// Right-aligns and pads given _bytes and returns a new
/// bytes array.
static bytes alignRight(bytes _bytes);
/// Applies given _alignment to _bytes and returns a new
/// bytes array.
/// TODO: Remove abiType reference from parameter list
/// and return the new alignment instead.
static bytes applyAlign(
Parameter::Alignment _alignment,
ABIType& _abiType,
bytes _bytes
);
/// Tries to convert \param _literal to an unpadded `bytes` /// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the boolean number literal. Throws if conversion fails. /// representation of the boolean number literal. Throws if conversion fails.
bytes convertBoolean(std::string const& _literal); static bytes convertBoolean(std::string const& _literal);
/// Tries to convert \param _literal to an unpadded `bytes` /// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the decimal number literal. Throws if conversion fails. /// representation of the decimal number literal. Throws if conversion fails.
bytes convertNumber(std::string const& _literal); static bytes convertNumber(std::string const& _literal);
/// Tries to convert \param _literal to an unpadded `bytes` /// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the hex literal. Throws if conversion fails. /// representation of the hex literal. Throws if conversion fails.
bytes convertHexNumber(std::string const& _literal); static bytes convertHexNumber(std::string const& _literal);
/// Tries to convert \param _literal to an unpadded `bytes` /// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the string literal. Throws if conversion fails. /// representation of the string literal. Throws if conversion fails.
bytes convertString(std::string const& _literal); static bytes convertString(std::string const& _literal);
/// Converts \param _bytes to a soltest-compliant and human-readable /// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold /// string representation of a byte array which is assumed to hold
/// an unsigned value. /// an unsigned value.
std::string formatUnsigned(bytes const& _bytes) const; static std::string formatUnsigned(bytes const& _bytes);
/// Converts \param _bytes to a soltest-compliant and human-readable /// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold /// string representation of a byte array which is assumed to hold
/// a signed value. /// a signed value.
std::string formatSigned(bytes const& _bytes) const; static std::string formatSigned(bytes const& _bytes);
/// Converts \param _bytes to a soltest-compliant and human-readable /// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold /// string representation of a byte array which is assumed to hold
/// a boolean value. /// a boolean value.
std::string formatBoolean(bytes const& _bytes) const; static std::string formatBoolean(bytes const& _bytes);
/// Converts \param _bytes to a soltest-compliant and human-readable /// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold /// string representation of a byte array which is assumed to hold
/// a hex value. /// a hex value.
std::string formatHex(bytes const& _bytes) const; static std::string formatHex(bytes const& _bytes);
/// Converts \param _bytes to a soltest-compliant and human-readable /// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold /// string representation of a byte array which is assumed to hold
/// a hexString value. /// a hexString value.
std::string formatHexString(bytes const& _bytes) const; static std::string formatHexString(bytes const& _bytes);
/// Converts \param _bytes to a soltest-compliant and human-readable /// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold /// string representation of a byte array which is assumed to hold
/// a string value. /// a string value.
std::string formatString(bytes const& _bytes, size_t _cutOff) const; static std::string formatString(bytes const& _bytes, size_t _cutOff);
std::string formatString(bytes const& _bytes) const static std::string formatString(bytes const& _bytes)
{ {
return formatString(_bytes, _bytes.size()); return formatString(_bytes, _bytes.size());
} }
/// Left-aligns and pads given _bytes and returns a new /// Returns a string representation of given _bytes. Adds a newline
/// bytes array. /// every 32 bytes to increase readability.
bytes alignLeft(bytes _bytes) const; /// Used to print returned bytes from function calls to the commandline.
static std::string formatRawBytes(bytes const& _bytes);
/// Right-aligns and pads given _bytes and returns a new /// Formats given _bytes with type information passed in _abiType.
/// bytes array. static std::string formatBytes(bytes const& _bytes, ABIType const& _abiType);
bytes alignRight(bytes _bytes) const;
/// Applies given _alignment to _bytes and returns a new /// Formats given _bytes with type information passed in _abiTypes.
/// bytes array. /// Prints obtained result if it does not match the expectation
bytes applyAlign( /// and prints the expected result otherwise.
Parameter::Alignment _alignment, /// Highlights parameter only if it does not match.
ABIType& _abiType, static std::string formatBytesRange(
bytes _bytes bytes _bytes,
) const; ParameterList const& _parameters,
bool _highlight
);
}; };
} }

View File

@ -17,12 +17,18 @@
#include <test/libsolidity/util/ContractABIUtils.h> #include <test/libsolidity/util/ContractABIUtils.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <liblangutil/Common.h> #include <liblangutil/Common.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <numeric>
#include <regex> #include <regex>
#include <stdexcept> #include <stdexcept>
@ -33,55 +39,266 @@ using namespace dev::solidity::test;
using namespace std; using namespace std;
using namespace soltest; using namespace soltest;
dev::solidity::test::ParameterList ContractABIUtils::parametersFromJson( namespace
Json::Value const& _contractABI,
string const& _functionName
) const
{ {
ParameterList abiParams;
using ParameterList = dev::solidity::test::ParameterList;
size_t arraySize(string const& _arrayType)
{
auto leftBrack = _arrayType.find("[");
auto rightBrack = _arrayType.rfind("]");
soltestAssert(
leftBrack != string::npos &&
rightBrack != string::npos &&
rightBrack == _arrayType.size() - 1 &&
leftBrack < rightBrack,
""
);
string size = _arrayType.substr(leftBrack + 1, rightBrack - leftBrack - 1);
return static_cast<size_t>(stoi(size));
}
bool isBool(string const& _type)
{
return _type == "bool";
}
bool isUint(string const& _type)
{
return regex_match(_type, regex{"uint\\d*"});
}
bool isInt(string const& _type)
{
return regex_match(_type, regex{"int\\d*"});
}
bool isFixedBytes(string const& _type)
{
return regex_match(_type, regex{"bytes\\d+"});
}
bool isBytes(string const& _type)
{
return regex_match(_type, regex{"\\bbytes\\b"});
}
bool isString(string const& _type)
{
return _type == "string";
}
bool isFixedBoolArray(string const& _type)
{
return regex_match(_type, regex{"bool\\[\\d+\\]"});
}
bool isFixedUintArray(string const& _type)
{
return regex_match(_type, regex{"uint\\d*\\[\\d+\\]"});
}
bool isFixedIntArray(string const& _type)
{
return regex_match(_type, regex{"int\\d*\\[\\d+\\]"});
}
bool isFixedStringArray(string const& _type)
{
return regex_match(_type, regex{"string\\[\\d+\\]"});
}
bool isTuple(string const& _type)
{
return _type == "tuple";
}
bool isFixedTupleArray(string const& _type)
{
return regex_match(_type, regex{"tuple\\[\\d+\\]"});
}
string functionSignatureFromABI(Json::Value const& _functionABI)
{
auto inputs = _functionABI["inputs"];
string signature = {_functionABI["name"].asString() + "("};
size_t parameterCount = 0;
for (auto const& input: inputs)
{
parameterCount++;
signature += input["type"].asString();
if (parameterCount < inputs.size())
signature += ",";
}
return signature + ")";
}
}
boost::optional<dev::solidity::test::ParameterList> ContractABIUtils::parametersFromJsonOutputs(
ErrorReporter& _errorReporter,
Json::Value const& _contractABI,
string const& _functionSignature
)
{
if (!_contractABI)
return boost::none;
for (auto const& function: _contractABI) for (auto const& function: _contractABI)
if (function["name"] == _functionName) if (_functionSignature == functionSignatureFromABI(function))
{
ParameterList inplaceTypeParams;
ParameterList dynamicTypeParams;
ParameterList finalParams;
for (auto const& output: function["outputs"]) for (auto const& output: function["outputs"])
{ {
auto types = fromTypeName(output["type"].asString()); string type = output["type"].asString();
for (auto const& type: types)
abiParams.push_back(Parameter{bytes(), "", type, FormatInfo{}});
}
return abiParams; ABITypes inplaceTypes;
ABITypes dynamicTypes;
if (appendTypesFromName(output, inplaceTypes, dynamicTypes))
{
for (auto const& type: inplaceTypes)
inplaceTypeParams.push_back(Parameter{bytes(), "", type, FormatInfo{}});
for (auto const& type: dynamicTypes)
dynamicTypeParams.push_back(Parameter{bytes(), "", type, FormatInfo{}});
}
else
{
_errorReporter.warning(
"Could not convert \"" + type +
"\" to internal ABI type representation. Falling back to default encoding."
);
return boost::none;
}
finalParams += inplaceTypeParams;
inplaceTypeParams.clear();
}
return boost::optional<ParameterList>(finalParams + dynamicTypeParams);
}
return boost::none;
} }
std::vector<ABIType> ContractABIUtils::fromTypeName(string const& _type) const bool ContractABIUtils::appendTypesFromName(
Json::Value const& _functionOutput,
ABITypes& _inplaceTypes,
ABITypes& _dynamicTypes,
bool _isCompoundType
)
{ {
static regex s_boolType{"(bool)"}; string type = _functionOutput["type"].asString();
static regex s_uintType{"(uint\\d*)"}; if (isBool(type))
static regex s_intType{"(int\\d*)"}; _inplaceTypes.push_back(ABIType{ABIType::Boolean});
static regex s_bytesType{"(bytes\\d+)"}; else if (isUint(type))
static regex s_dynBytesType{"(\\bbytes\\b)"}; _inplaceTypes.push_back(ABIType{ABIType::UnsignedDec});
static regex s_stringType{"(string)"}; else if (isInt(type))
_inplaceTypes.push_back(ABIType{ABIType::SignedDec});
else if (isFixedBytes(type))
_inplaceTypes.push_back(ABIType{ABIType::Hex});
else if (isString(type))
{
_inplaceTypes.push_back(ABIType{ABIType::Hex});
vector<ABIType> abiTypes; if (_isCompoundType)
if (regex_match(_type, s_boolType)) _dynamicTypes.push_back(ABIType{ABIType::Hex});
abiTypes.push_back(ABIType{ABIType::Boolean, ABIType::AlignRight, 32});
else if (regex_match(_type, s_uintType)) _dynamicTypes.push_back(ABIType{ABIType::UnsignedDec});
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32}); _dynamicTypes.push_back(ABIType{ABIType::String, ABIType::AlignLeft});
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)) else if (isTuple(type))
{ {
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32}); ABITypes inplaceTypes;
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32}); ABITypes dynamicTypes;
abiTypes.push_back(ABIType{ABIType::String, ABIType::AlignLeft, 32});
for (auto const& component: _functionOutput["components"])
appendTypesFromName(component, inplaceTypes, dynamicTypes, true);
_dynamicTypes += inplaceTypes + dynamicTypes;
}
else if (isFixedBoolArray(type))
_inplaceTypes += vector<ABIType>(arraySize(type), ABIType{ABIType::Boolean});
else if (isFixedUintArray(type))
_inplaceTypes += vector<ABIType>(arraySize(type), ABIType{ABIType::UnsignedDec});
else if (isFixedIntArray(type))
_inplaceTypes += vector<ABIType>(arraySize(type), ABIType{ABIType::SignedDec});
else if (isFixedStringArray(type))
{
_inplaceTypes.push_back(ABIType{ABIType::Hex});
_dynamicTypes += vector<ABIType>(arraySize(type), ABIType{ABIType::Hex});
for (size_t i = 0; i < arraySize(type); i++)
{
_dynamicTypes.push_back(ABIType{ABIType::UnsignedDec});
_dynamicTypes.push_back(ABIType{ABIType::String, ABIType::AlignLeft});
}
}
else if (isBytes(type))
return false;
else if (isFixedTupleArray(type))
return false;
else
return false;
return true;
}
void ContractABIUtils::overwriteParameters(
ErrorReporter& _errorReporter,
dev::solidity::test::ParameterList& _targetParameters,
dev::solidity::test::ParameterList const& _sourceParameters
)
{
boost::for_each(
_sourceParameters,
_targetParameters,
boost::bind<void>(
[&](Parameter _a, Parameter& _b) -> void
{
if (
_a.abiType.size != _b.abiType.size ||
_a.abiType.type != _b.abiType.type
)
{
_errorReporter.warning("Type or size of parameter(s) does not match.");
_b = _a;
}
},
_1,
_2
)
);
}
dev::solidity::test::ParameterList ContractABIUtils::preferredParameters(
ErrorReporter& _errorReporter,
dev::solidity::test::ParameterList const& _targetParameters,
dev::solidity::test::ParameterList const& _sourceParameters,
bytes const& _bytes
)
{
if (_targetParameters.size() != _sourceParameters.size())
{
auto sizeFold = [](size_t const _a, Parameter const& _b) { return _a + _b.abiType.size; };
size_t encodingSize = accumulate(_targetParameters.begin(), _targetParameters.end(), size_t{0}, sizeFold);
_errorReporter.warning(
"Encoding does not match byte range. The call returned " +
to_string(_bytes.size()) + " bytes, but " +
to_string(encodingSize) + " bytes were expected."
);
return _sourceParameters;
} }
else else
abiTypes.push_back(ABIType{ABIType::None, ABIType::AlignRight, 0}); return _targetParameters;
return abiTypes;
} }

View File

@ -16,6 +16,8 @@
#include <test/libsolidity/util/SoltestTypes.h> #include <test/libsolidity/util/SoltestTypes.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
#include <json/json.h> #include <json/json.h>
@ -27,6 +29,8 @@ namespace solidity
namespace test namespace test
{ {
using ABITypes = std::vector<ABIType>;
/** /**
* Utility class that aids conversions from contract ABI types stored in a * Utility class that aids conversions from contract ABI types stored in a
* Json value to the internal ABIType representation of isoltest. * Json value to the internal ABIType representation of isoltest.
@ -36,10 +40,30 @@ class ContractABIUtils
public: public:
/// Parses and translates Solidity's ABI types as Json string into /// Parses and translates Solidity's ABI types as Json string into
/// a list of internal type representations of isoltest. /// a list of internal type representations of isoltest.
ParameterList parametersFromJson( /// Creates parameters from Contract ABI and is used to generate values for
/// auto-correction during interactive update routine.
static boost::optional<ParameterList> parametersFromJsonOutputs(
ErrorReporter& _errorReporter,
Json::Value const& _contractABI, Json::Value const& _contractABI,
std::string const& _functionName std::string const& _functionSignature
) const; );
/// Overwrites _targetParameters if ABI types or sizes given
/// by _sourceParameters do not match.
static void overwriteParameters(
ErrorReporter& _errorReporter,
ParameterList& _targetParameters,
ParameterList const& _sourceParameters
);
/// If parameter count does not match, take types defined _sourceParameters
/// and create a warning if so.
static ParameterList preferredParameters(
ErrorReporter& _errorReporter,
ParameterList const& _targetParameters,
ParameterList const& _sourceParameters,
bytes const& _bytes
);
private: private:
/// Parses and translates a single type and returns a list of /// Parses and translates a single type and returns a list of
@ -51,7 +75,12 @@ private:
/// `string` -> [`Unsigned`, `Unsigned`, `String`] /// `string` -> [`Unsigned`, `Unsigned`, `String`]
/// `bytes` -> [`Unsigned`, `Unsigned`, `HexString`] /// `bytes` -> [`Unsigned`, `Unsigned`, `HexString`]
/// ... /// ...
std::vector<ABIType> fromTypeName(std::string const& _type) const; static bool appendTypesFromName(
Json::Value const& _functionOutput,
ABITypes& _inplaceTypes,
ABITypes& _dynamicTypes,
bool _isCompoundType = false
);
}; };
} }

View File

@ -0,0 +1,128 @@
/*
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/AnsiColorized.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/Exceptions.h>
namespace dev
{
namespace solidity
{
namespace test
{
#define soltestAssert(CONDITION, DESCRIPTION) \
do \
{ \
if (!(CONDITION)) \
BOOST_THROW_EXCEPTION(runtime_error(DESCRIPTION)); \
} \
while (false)
/**
* Representation of a notice, warning or error that can occur while
* formatting and therefore updating an interactive function call test.
*/
struct FormatError
{
enum Type
{
Notice,
Warning,
Error
};
explicit FormatError(Type _type, std::string _message):
type(_type),
message(std::move(_message))
{}
Type type;
std::string message;
};
using FormatErrors = std::vector<FormatError>;
/**
* Utility class that collects notices, warnings and errors and is able
* to format them for ANSI colorized output during the interactive update
* process in isoltest.
* Its purpose is to help users of isoltest to automatically
* update test files and always keep track of what is happening.
*/
class ErrorReporter
{
public:
explicit ErrorReporter() {}
/// Adds a new FormatError of type Notice with the given message.
void notice(std::string _notice)
{
m_errors.push_back(FormatError{FormatError::Notice, std::move(_notice)});
}
/// Adds a new FormatError of type Warning with the given message.
void warning(std::string _warning)
{
m_errors.push_back(FormatError{FormatError::Warning, std::move(_warning)});
}
/// Adds a new FormatError of type Error with the given message.
void error(std::string _error)
{
m_errors.push_back(FormatError{FormatError::Error, std::move(_error)});
}
/// Prints all errors depending on their type using ANSI colorized output.
/// It will be used to print notices, warnings and errors during the
/// interactive update process.
std::string format(std::string const& _linePrefix, bool _formatted)
{
std::stringstream os;
for (auto const& error: m_errors)
{
switch (error.type)
{
case FormatError::Notice:
break;
case FormatError::Warning:
AnsiColorized(
os,
_formatted,
{formatting::YELLOW}
) << _linePrefix << "Warning: " << error.message << std::endl;
break;
case FormatError::Error:
AnsiColorized(
os,
_formatted,
{formatting::RED}
) << _linePrefix << "Error: " << error.message << std::endl;
break;
}
}
return os.str();
}
private:
FormatErrors m_errors;
};
}
}
}

View File

@ -14,6 +14,7 @@
#pragma once #pragma once
#include <libdevcore/AnsiColorized.h>
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
#include <libsolidity/ast/Types.h> #include <libsolidity/ast/Types.h>
@ -111,9 +112,23 @@ struct ABIType
AlignRight, AlignRight,
AlignNone, AlignNone,
}; };
explicit ABIType(
Type _type,
Align _align = ABIType::AlignRight,
size_t _size = 32
): type(_type), align(_align), size(_size) {}
ABIType(ABIType const& _other):
type(_other.type),
align(_other.align),
size(_other.size),
alignDeclared(_other.alignDeclared)
{}
Type type = ABIType::None; Type type = ABIType::None;
Align align = ABIType::AlignRight; Align align = ABIType::AlignRight;
size_t size = 0; size_t size = 32;
bool alignDeclared = false; bool alignDeclared = false;
}; };
@ -130,7 +145,7 @@ struct FormatInfo
* Parameter abstraction used for the encoding and decoding of * Parameter abstraction used for the encoding and decoding of
* function parameter and expectation / return value lists. * function parameter and expectation / return value lists.
* A parameter list is usually a comma-separated list of literals. * A parameter list is usually a comma-separated list of literals.
* It should not be possible to call create a parameter holding * It should not be possible to create a parameter holding
* an identifier, but if so, the ABI type would be invalid. * an identifier, but if so, the ABI type would be invalid.
*/ */
struct Parameter struct Parameter
@ -154,12 +169,17 @@ struct Parameter
/// Types that were used to encode `rawBytes`. Expectations /// Types that were used to encode `rawBytes`. Expectations
/// are usually comma separated literals. Their type is auto- /// are usually comma separated literals. Their type is auto-
/// detected and retained in order to format them later on. /// detected and retained in order to format them later on.
ABIType abiType; ABIType abiType = ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32};
/// Format info attached to the parameter. It handles newlines given /// Format info attached to the parameter. It handles newlines given
/// in the declaration of it. /// in the declaration of it.
FormatInfo format; FormatInfo format;
/// Stores the parsed alignment, which can be either left(...) or right(...). /// Stores the parsed alignment, which can be either left(...) or right(...).
Alignment alignment = Alignment::None; Alignment alignment = Alignment::None;
/// Compares _bytes to the bytes stored in this object.
bool matchesBytes(bytes const& _bytes) const
{
return rawBytes == _bytes;
}
}; };
using ParameterList = std::vector<Parameter>; using ParameterList = std::vector<Parameter>;

View File

@ -234,9 +234,9 @@ Parameter TestFileParser::parseParameter()
parameter.format.newline = true; parameter.format.newline = true;
m_lineNumber++; m_lineNumber++;
} }
parameter.abiType = ABIType{ABIType::None, ABIType::AlignNone, 0};
bool isSigned = false; bool isSigned = false;
if (accept(Token::Left, true)) if (accept(Token::Left, true))
{ {
parameter.rawString += formatToken(Token::Left); parameter.rawString += formatToken(Token::Left);
@ -267,10 +267,10 @@ Parameter TestFileParser::parseParameter()
parameter.abiType = ABIType{ABIType::Boolean, ABIType::AlignRight, 32}; parameter.abiType = ABIType{ABIType::Boolean, ABIType::AlignRight, 32};
string parsed = parseBoolean(); string parsed = parseBoolean();
parameter.rawString += parsed; parameter.rawString += parsed;
parameter.rawBytes = BytesUtils().applyAlign( parameter.rawBytes = BytesUtils::applyAlign(
parameter.alignment, parameter.alignment,
parameter.abiType, parameter.abiType,
BytesUtils().convertBoolean(parsed) BytesUtils::convertBoolean(parsed)
); );
} }
else if (accept(Token::HexNumber)) else if (accept(Token::HexNumber))
@ -281,10 +281,10 @@ Parameter TestFileParser::parseParameter()
parameter.abiType = ABIType{ABIType::Hex, ABIType::AlignRight, 32}; parameter.abiType = ABIType{ABIType::Hex, ABIType::AlignRight, 32};
string parsed = parseHexNumber(); string parsed = parseHexNumber();
parameter.rawString += parsed; parameter.rawString += parsed;
parameter.rawBytes = BytesUtils().applyAlign( parameter.rawBytes = BytesUtils::applyAlign(
parameter.alignment, parameter.alignment,
parameter.abiType, parameter.abiType,
BytesUtils().convertHexNumber(parsed) BytesUtils::convertHexNumber(parsed)
); );
} }
else if (accept(Token::Hex, true)) else if (accept(Token::Hex, true))
@ -296,7 +296,7 @@ Parameter TestFileParser::parseParameter()
string parsed = parseString(); string parsed = parseString();
parameter.rawString += "hex\"" + parsed + "\""; parameter.rawString += "hex\"" + parsed + "\"";
parameter.rawBytes = BytesUtils().convertHexNumber(parsed); parameter.rawBytes = BytesUtils::convertHexNumber(parsed);
parameter.abiType = ABIType{ parameter.abiType = ABIType{
ABIType::HexString, ABIType::AlignNone, parameter.rawBytes.size() ABIType::HexString, ABIType::AlignNone, parameter.rawBytes.size()
}; };
@ -309,12 +309,12 @@ Parameter TestFileParser::parseParameter()
throw Error(Error::Type::ParserError, "String literals cannot be aligned or padded."); throw Error(Error::Type::ParserError, "String literals cannot be aligned or padded.");
string parsed = parseString(); string parsed = parseString();
parameter.abiType = {ABIType::String, ABIType::AlignLeft, parsed.size()}; parameter.abiType = ABIType{ABIType::String, ABIType::AlignLeft, parsed.size()};
parameter.rawString += "\"" + parsed + "\""; parameter.rawString += "\"" + parsed + "\"";
parameter.rawBytes = BytesUtils().applyAlign( parameter.rawBytes = BytesUtils::applyAlign(
Parameter::Alignment::Left, Parameter::Alignment::Left,
parameter.abiType, parameter.abiType,
BytesUtils().convertString(parsed) BytesUtils::convertString(parsed)
); );
} }
else if (accept(Token::Number)) else if (accept(Token::Number))
@ -327,10 +327,10 @@ Parameter TestFileParser::parseParameter()
if (isSigned) if (isSigned)
parsed = "-" + parsed; parsed = "-" + parsed;
parameter.rawBytes = BytesUtils().applyAlign( parameter.rawBytes = BytesUtils::applyAlign(
parameter.alignment, parameter.alignment,
parameter.abiType, parameter.abiType,
BytesUtils().convertNumber(parsed) BytesUtils::convertNumber(parsed)
); );
} }
else if (accept(Token::Failure, true)) else if (accept(Token::Failure, true))

View File

@ -20,6 +20,7 @@
#include <libdevcore/AnsiColorized.h> #include <libdevcore/AnsiColorized.h>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
#include <boost/optional/optional.hpp>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@ -98,7 +99,6 @@ string TestFunctionCall::format(
string result; string result;
if (!_renderResult) if (!_renderResult)
{ {
bytes output = m_call.expectations.rawBytes();
bool const isFailure = m_call.expectations.failure; bool const isFailure = m_call.expectations.failure;
result = isFailure ? result = isFailure ?
failure : failure :
@ -154,129 +154,49 @@ string TestFunctionCall::formatBytesParameters(
ErrorReporter& _errorReporter, ErrorReporter& _errorReporter,
bytes const& _bytes, bytes const& _bytes,
string const& _signature, string const& _signature,
dev::solidity::test::ParameterList const& _params, dev::solidity::test::ParameterList const& _parameters,
bool _highlight bool _highlight
) const ) const
{ {
using ParameterList = dev::solidity::test::ParameterList; using ParameterList = dev::solidity::test::ParameterList;
stringstream os; stringstream os;
string functionName{_signature.substr(0, _signature.find("("))};
/// Create parameters from Contract ABI. Used to generate values for
/// auto-correction during interactive update routine.
ParameterList abiParams = ContractABIUtils().parametersFromJson(m_contractABI, functionName);
/// 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
/// result does not matter).
ParameterList preferredParams;
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(
"Encoding does not match byte range. The call returned " +
to_string(_bytes.size()) + " bytes, but " +
to_string(encodingSize) + " bytes were expected."
);
preferredParams = abiParams;
}
else
preferredParams = _params;
/// If output is empty, do not format anything.
if (_bytes.empty()) if (_bytes.empty())
return {}; return {};
/// Format output bytes with the given parameters. ABI type takes precedence if: _errorReporter.warning("The call to \"" + _signature + "\" returned \n" + BytesUtils::formatRawBytes(_bytes));
/// - size of ABI type is greater
/// - given expected type does not match and needs to be overridden in order boost::optional<ParameterList> abiParams = ContractABIUtils::parametersFromJsonOutputs(
/// to generate a valid output of the parameter _errorReporter,
auto it = _bytes.begin(); m_contractABI,
auto abiParam = abiParams.begin(); _signature
size_t paramIndex = 1; );
for (auto const& param: preferredParams)
if (abiParams)
{ {
size_t size = param.abiType.size; boost::optional<ParameterList> preferredParams = ContractABIUtils::preferredParameters(
if (m_contractABI) _errorReporter,
size = std::max((*abiParam).abiType.size, param.abiType.size); _parameters,
abiParams.get(),
_bytes
);
long offset = static_cast<long>(size); if (preferredParams)
auto offsetIter = it + offset; {
bytes byteRange{it, offsetIter}; ContractABIUtils::overwriteParameters(_errorReporter, preferredParams.get(), abiParams.get());
os << BytesUtils::formatBytesRange(_bytes, preferredParams.get(), _highlight);
/// Override type with ABI type if given one does not match. }
auto type = param.abiType;
if (m_contractABI)
if ((*abiParam).abiType.type > param.abiType.type)
{
type = (*abiParam).abiType;
_errorReporter.warning(
"Type of parameter " + to_string(paramIndex) +
" does not match the one inferred from ABI."
);
}
/// Prints obtained result if it does not match the expectation
/// and prints the expected result otherwise.
/// Highlights parameter only if it does not match.
if (byteRange != param.rawBytes)
AnsiColorized(
os,
_highlight,
{dev::formatting::RED_BACKGROUND}
) << formatBytesRange(byteRange, type);
else
os << param.rawString;
if (abiParam != abiParams.end())
abiParam++;
it += offset;
paramIndex++;
if (&param != &preferredParams.back())
os << ", ";
} }
return os.str(); else
}
string TestFunctionCall::formatBytesRange(
bytes const& _bytes,
ABIType const& _abiType
) const
{
stringstream os;
switch (_abiType.type)
{ {
case ABIType::UnsignedDec: ParameterList defaultParameters;
// Check if the detected type was wrong and if this could fill_n(
// be signed. If an unsigned was detected in the expectations, back_inserter(defaultParameters),
// but the actual result returned a signed, it would be formatted ceil(_bytes.size() / 32),
// incorrectly. Parameter{bytes(), "", ABIType{ABIType::UnsignedDec}, FormatInfo{}}
os << BytesUtils().formatUnsigned(_bytes); );
break; ContractABIUtils::overwriteParameters(_errorReporter, defaultParameters, _parameters);
case ABIType::SignedDec: os << BytesUtils::formatBytesRange(_bytes, defaultParameters, _highlight);
os << BytesUtils().formatSigned(_bytes);
break;
case ABIType::Boolean:
os << BytesUtils().formatBoolean(_bytes);
break;
case ABIType::Hex:
os << BytesUtils().formatHex(_bytes);
break;
case ABIType::HexString:
os << BytesUtils().formatHexString(_bytes);
break;
case ABIType::String:
os << BytesUtils().formatString(_bytes, _abiType.size);
break;
case ABIType::Failure:
break;
case ABIType::None:
break;
} }
return os.str(); return os.str();
} }
@ -288,18 +208,14 @@ string TestFunctionCall::formatRawParameters(
{ {
stringstream os; stringstream os;
for (auto const& param: _params) for (auto const& param: _params)
{ if (!param.rawString.empty())
if (param.format.newline)
os << endl << _linePrefix << "// ";
os << param.rawString;
if (&param != &_params.back())
{ {
if (param.format.newline) if (param.format.newline)
os << ","; os << endl << _linePrefix << "// ";
else os << param.rawString;
if (&param != &_params.back())
os << ", "; os << ", ";
} }
}
return os.str(); return os.str();
} }

View File

@ -15,7 +15,7 @@
#pragma once #pragma once
#include <test/libsolidity/util/TestFileParser.h> #include <test/libsolidity/util/TestFileParser.h>
#include <test/TestCase.h> #include <test/libsolidity/util/SoltestErrors.h>
#include <libsolidity/ast/Types.h> #include <libsolidity/ast/Types.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
@ -37,95 +37,6 @@ namespace solidity
namespace test namespace test
{ {
/**
* Representation of a notice, warning or error that can occur while
* formatting and therefore updating an interactive function call test.
*/
struct FormatError
{
enum Type
{
Notice,
Warning,
Error
};
explicit FormatError(Type _type, std::string _message):
type(_type),
message(std::move(_message))
{}
Type type;
std::string message;
};
using FormatErrors = std::vector<FormatError>;
/**
* Utility class that collects notices, warnings and errors and is able
* to format them for ANSI colorized output during the interactive update
* process in isoltest.
* Its purpose is to help users of isoltest to automatically
* update test files and always keep track of what is happening.
*/
class ErrorReporter
{
public:
explicit ErrorReporter() {}
/// Adds a new FormatError of type Notice with the given message.
void notice(std::string _notice)
{
m_errors.push_back(FormatError{FormatError::Notice, std::move(_notice)});
}
/// Adds a new FormatError of type Warning with the given message.
void warning(std::string _warning)
{
m_errors.push_back(FormatError{FormatError::Warning, std::move(_warning)});
}
/// Adds a new FormatError of type Error with the given message.
void error(std::string _error)
{
m_errors.push_back(FormatError{FormatError::Error, std::move(_error)});
}
/// Prints all errors depending on their type using ANSI colorized output.
/// It will be used to print notices, warnings and errors during the
/// interactive update process.
std::string format(std::string const& _linePrefix, bool _formatted)
{
std::stringstream os;
for (auto const& error: m_errors)
{
switch (error.type)
{
case FormatError::Notice:
break;
case FormatError::Warning:
AnsiColorized(
os,
_formatted,
{formatting::YELLOW}
) << _linePrefix << "Warning: " << error.message << std::endl;
break;
case FormatError::Error:
AnsiColorized(
os,
_formatted,
{formatting::RED}
) << _linePrefix << "Error: " << error.message << std::endl;
break;
}
}
return os.str();
}
private:
FormatErrors m_errors;
};
/** /**
* Represents a function call and the result it returned. It stores the call * Represents a function call and the result it returned. It stores the call
* representation itself, the actual byte result (if any) and a string representation * representation itself, the actual byte result (if any) and a string representation

View File

@ -17,6 +17,7 @@
#include <test/libyul/YulOptimizerTest.h> #include <test/libyul/YulOptimizerTest.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <test/Options.h> #include <test/Options.h>
#include <libyul/optimiser/BlockFlattener.h> #include <libyul/optimiser/BlockFlattener.h>