diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 2fddc9f44..852101a37 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -74,6 +74,7 @@ bool SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _format test.setFailure(!m_transactionSuccessful); test.setRawBytes(std::move(output)); + test.setContractABI(m_compiler.contractABI(m_compiler.lastContractName())); } if (!success) diff --git a/test/libsolidity/semanticTests/smoke_test.sol b/test/libsolidity/semanticTests/smoke_test.sol index d25e61b8a..ef7c55240 100644 --- a/test/libsolidity/semanticTests/smoke_test.sol +++ b/test/libsolidity/semanticTests/smoke_test.sol @@ -1,31 +1,35 @@ contract C { - function f() public returns (uint) { + function f() payable public returns (uint) { return 2; } - function g(uint x, uint y) public returns (uint) { + function g() public returns (uint, uint) { + return (2, 3); + } + function h(uint x, uint y) public returns (uint) { return x - y; } - function h() public payable returns (uint) { - return f(); - } - function i(bytes32 b) public returns (bytes32) { - return b; - } function j(bool b) public returns (bool) { return !b; } - function k(bytes32 b) public returns (bytes32) { - return b; + function k(bytes32 b) public returns (bytes32, bytes32) { + return (b, b); } - function s() public returns (uint256) { + function l() public returns (uint256) { return msg.data.length; } + function m(bytes memory b) public returns (bytes memory) { + return b; + } } // ---- +// _() -> FAILURE // f() -> 2 -// g(uint256,uint256): 1, -2 -> 3 -// h(), 1 ether -> 2 -// i() -> FAILURE +// f(), 1 ether -> 2 +// g() -> 2, 3 +// h(uint256,uint256): 1, -2 -> 3 // j(bool): true -> false -// k(bytes32): 0x31 -> 0x31 -// s(): hex"4200ef" -> 7 +// k(bytes32): 0x10 -> 0x10, 0x10 +// l(): hex"4200ef" -> 7 +// m(bytes): 32, 32, 0x20 -> 32, 32, 0x20 +// m(bytes): 32, 3, hex"AB33BB" -> 32, 3, left(0xAB33BB) +// m(bytes): 32, 3, hex"AB33FF" -> 32, 3, hex"ab33ff0000000000000000000000000000000000000000000000000000000000" diff --git a/test/libsolidity/util/TestFileParser.h b/test/libsolidity/util/TestFileParser.h index fb1cc71a2..829283e38 100644 --- a/test/libsolidity/util/TestFileParser.h +++ b/test/libsolidity/util/TestFileParser.h @@ -105,13 +105,13 @@ struct ABIType { enum Type { + None, + Failure, + Boolean, UnsignedDec, SignedDec, - Boolean, Hex, - HexString, - Failure, - None + HexString }; enum Align { diff --git a/test/libsolidity/util/TestFunctionCall.cpp b/test/libsolidity/util/TestFunctionCall.cpp index 72b3e02c0..e34917614 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -13,7 +13,12 @@ */ #include + #include + +#include + +#include #include #include @@ -22,6 +27,40 @@ using namespace solidity; using namespace dev::solidity::test; 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)"}; + +/// Translates Solidity's ABI types into the internal type representation of +/// soltest. +auto contractABITypes(string const& _type) -> vector +{ + vector 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 + abiTypes.push_back(ABIType{ABIType::None, ABIType::AlignRight, 0}); + return abiTypes; +}; +} + string TestFunctionCall::format( ErrorReporter& _errorReporter, string const& _linePrefix, @@ -89,6 +128,7 @@ string TestFunctionCall::format( result = isFailure ? failure : formatRawParameters(m_call.expectations.result); + AnsiColorized(stream, highlight, {dev::formatting::RED_BACKGROUND}) << result; } else { @@ -98,9 +138,19 @@ string TestFunctionCall::format( failure : matchesExpectation() ? formatRawParameters(m_call.expectations.result) : - formatBytesParameters(_errorReporter, output, m_call.expectations.result); + formatBytesParameters( + _errorReporter, + output, + m_call.signature, + m_call.expectations.result, + highlight + ); + + if (isFailure) + AnsiColorized(stream, highlight, {dev::formatting::RED_BACKGROUND}) << result; + else + stream << result; } - AnsiColorized(stream, highlight, {dev::formatting::RED_BACKGROUND}) << result; /// Format comments on expectations taking the display-mode into account. if (_singleLine) @@ -125,88 +175,173 @@ string TestFunctionCall::format( string TestFunctionCall::formatBytesParameters( ErrorReporter& _errorReporter, bytes const& _bytes, - dev::solidity::test::ParameterList const& _params + string const& _signature, + dev::solidity::test::ParameterList const& _params, + bool _highlight ) const { - stringstream resultStream; + using ParameterList = dev::solidity::test::ParameterList; + + stringstream os; + string functionName{_signature.substr(0, _signature.find("("))}; - if (_bytes.empty()) - return {}; 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); - if (encodingSize != _bytes.size()) + /// Infer type from Contract ABI. Used to generate values for + /// auto-correction during interactive update routine. + ParameterList abiParams; + 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 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())) + { _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()) + return {}; + + /// Format output bytes with the given parameters. ABI type takes precedence if: + /// - size of ABI type is greater + /// - given expected type does not match and needs to be overridden in order + /// to generate a valid output of the parameter auto it = _bytes.begin(); - for (auto const& param: _params) + auto abiParam = abiParams.begin(); + size_t paramIndex = 1; + for (auto const& param: preferredParams) { - long offset = static_cast(param.abiType.size); + size_t size = param.abiType.size; + if (m_contractABI) + size = std::max((*abiParam).abiType.size, param.abiType.size); + + long offset = static_cast(size); auto offsetIter = it + offset; bytes byteRange{it, offsetIter}; - switch (param.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 (*byteRange.begin() & 0x80) - resultStream << u2s(fromBigEndian(byteRange)); - else - resultStream << fromBigEndian(byteRange); - break; - case ABIType::SignedDec: - if (*byteRange.begin() & 0x80) - resultStream << u2s(fromBigEndian(byteRange)); - else - resultStream << fromBigEndian(byteRange); - break; - case ABIType::Boolean: - { - u256 result = fromBigEndian(byteRange); - if (result == 0) - resultStream << "false"; - else if (result == 1) - resultStream << "true"; - else - resultStream << result; - break; - } - case ABIType::Hex: - resultStream << toHex(byteRange, HexPrefix::Add); - break; - case ABIType::HexString: - resultStream << "hex\"" << toHex(byteRange) << "\""; - break; - case ABIType::Failure: - break; - case ABIType::None: - break; - } + + /// 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; - if (it != _bytes.end() && !(param.abiType.type == ABIType::None)) - resultStream << ", "; + paramIndex++; + if (¶m != &preferredParams.back()) + os << ", "; } - return resultStream.str(); + return os.str(); } -string TestFunctionCall::formatRawParameters(dev::solidity::test::ParameterList const& _params, std::string const& _linePrefix) const +string TestFunctionCall::formatBytesRange( + bytes const& _bytes, + ABIType const& _abiType +) const { - stringstream resultStream; + 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 << u2s(fromBigEndian(_bytes)); + else + os << fromBigEndian(_bytes); + break; + case ABIType::SignedDec: + if (*_bytes.begin() & 0x80) + os << u2s(fromBigEndian(_bytes)); + else + os << fromBigEndian(_bytes); + break; + case ABIType::Boolean: + { + u256 result = fromBigEndian(_bytes); + if (result == 0) + os << "false"; + else if (result == 1) + os << "true"; + else + os << result; + break; + } + case ABIType::Hex: + { + string hex{toHex(_bytes, HexPrefix::Add)}; + boost::algorithm::replace_all(hex, "00", ""); + os << hex; + break; + } + case ABIType::HexString: + os << "hex\"" << toHex(_bytes) << "\""; + break; + case ABIType::Failure: + break; + case ABIType::None: + break; + } + return os.str(); +} + +string TestFunctionCall::formatRawParameters( + dev::solidity::test::ParameterList const& _params, + std::string const& _linePrefix +) const +{ + stringstream os; for (auto const& param: _params) { if (param.format.newline) - resultStream << endl << _linePrefix << "// "; - resultStream << param.rawString; + os << endl << _linePrefix << "// "; + os << param.rawString; if (¶m != &_params.back()) - resultStream << ", "; + os << ", "; } - return resultStream.str(); + return os.str(); } void TestFunctionCall::reset() diff --git a/test/libsolidity/util/TestFunctionCall.h b/test/libsolidity/util/TestFunctionCall.h index 8c0729c4e..1d8bc9e98 100644 --- a/test/libsolidity/util/TestFunctionCall.h +++ b/test/libsolidity/util/TestFunctionCall.h @@ -22,6 +22,8 @@ #include #include +#include + #include #include #include @@ -62,8 +64,8 @@ using FormatErrors = std::vector; * 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. - * It's purpose is to help users of isoltest to automatically - * update test files but always keep track of what is happening. + * Its purpose is to help users of isoltest to automatically + * update test files and always keep track of what is happening. */ class ErrorReporter { @@ -99,11 +101,7 @@ public: switch (error.type) { case FormatError::Notice: - AnsiColorized( - os, - _formatted, - {formatting::WHITE} - ) << _linePrefix << "Notice: " << error.message << std::endl; + break; case FormatError::Warning: AnsiColorized( @@ -145,6 +143,8 @@ public: /// the actual result is used. /// If _highlight is false, it's formatted without colorized highlighting. If it's true, AnsiColorized is /// used to apply a colorized highlighting. + /// If test expectations do not match, the contract ABI is consulted in order to get the + /// right encoding for returned bytes, based on the parsed return types. /// Reports warnings and errors to the error reporter. std::string format( ErrorReporter& _errorReporter, @@ -172,6 +172,7 @@ public: FunctionCall const& call() const { return m_call; } void setFailure(const bool _failure) { m_failure = _failure; } void setRawBytes(const bytes _rawBytes) { m_rawBytes = _rawBytes; } + void setContractABI(Json::Value _contractABI) { m_contractABI = std::move(_contractABI); } private: /// Tries to format the given `bytes`, applying the detected ABI types that have be set for each parameter. @@ -181,11 +182,22 @@ private: std::string formatBytesParameters( ErrorReporter& _errorReporter, bytes const& _bytes, - ParameterList const& _params + std::string const& _signature, + ParameterList const& _params, + bool highlight = false + ) const; + + /// Formats a given _bytes applying the _abiType. + std::string formatBytesRange( + bytes const& _bytes, + ABIType const& _abiType ) const; /// Formats the given parameters using their raw string representation. - std::string formatRawParameters(ParameterList const& _params, std::string const& _linePrefix = "") const; + std::string formatRawParameters( + ParameterList const& _params, + std::string const& _linePrefix = "" + ) const; /// 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. @@ -197,6 +209,9 @@ private: bytes m_rawBytes = bytes{}; /// Transaction status of the actual call. False in case of a REVERT or any other failure. bool m_failure = true; + /// JSON object which holds the contract ABI and that is used to set the output formatting + /// in the interactive update routine. + Json::Value m_contractABI; }; } diff --git a/test/libsolidity/util/TestFunctionCallTests.cpp b/test/libsolidity/util/TestFunctionCallTests.cpp index 662877946..9be123d05 100644 --- a/test/libsolidity/util/TestFunctionCallTests.cpp +++ b/test/libsolidity/util/TestFunctionCallTests.cpp @@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE(format_hex_singleline) test.setRawBytes(actualBytes); test.setFailure(false); - BOOST_REQUIRE_EQUAL(test.format("", true), "// f(bytes32): 0x31 -> 0x3200000000000000000000000000000000000000000000000000000000000000"); + BOOST_REQUIRE_EQUAL(test.format("", true), "// f(bytes32): 0x31 -> 0x32"); } BOOST_AUTO_TEST_CASE(format_hex_string_singleline) @@ -237,40 +237,6 @@ BOOST_AUTO_TEST_CASE(format_failure_singleline) BOOST_REQUIRE_EQUAL(test.format(), "// f(uint8): 1 -> FAILURE"); } -BOOST_AUTO_TEST_CASE(format_parameter_encoding_too_short) -{ - bytes expectedBytes = toBigEndian(u256{1}) + toBigEndian(u256{1}); - ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 20}; - Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param, param}, false, string{}}; - FunctionCallArgs arguments{vector{param, param}, string{}}; - FunctionCall call{"f(uint8, uint8)", 0, arguments, expectations}; - TestFunctionCall test{call}; - - bytes resultBytes = toBigEndian(u256{1}) + toBigEndian(u256{2}); - test.setRawBytes(resultBytes); - test.setFailure(false); - - BOOST_REQUIRE_THROW(test.format("", true), runtime_error); -} - -BOOST_AUTO_TEST_CASE(format_byte_range_too_short) -{ - bytes expectedBytes = toBigEndian(u256{1}); - ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; - Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param, param}, false, string{}}; - FunctionCallArgs arguments{vector{param, param}, string{}}; - FunctionCall call{"f(uint8, uint8)", 0, arguments, expectations}; - TestFunctionCall test{call}; - - bytes resultBytes{0}; - test.setRawBytes(resultBytes); - test.setFailure(false); - - BOOST_REQUIRE_THROW(test.format("", true), runtime_error); -} - BOOST_AUTO_TEST_SUITE_END() }