From 557b1727e033a39185c737946ef34817e54cd96b Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Thu, 7 Jan 2021 22:45:19 -0500 Subject: [PATCH 1/7] [isoltest] Initial support for builtin functions. --- liblangutil/Common.h | 5 ++ test/libsolidity/SemanticTest.cpp | 110 +++++++++++++++++++++-- test/libsolidity/SemanticTest.h | 9 ++ test/libsolidity/util/SoltestTypes.h | 8 +- test/libsolidity/util/TestFileParser.cpp | 24 ++++- test/libsolidity/util/TestFileParser.h | 4 +- 6 files changed, 149 insertions(+), 11 deletions(-) diff --git a/liblangutil/Common.h b/liblangutil/Common.h index d0f68a941805..68780eb15111 100644 --- a/liblangutil/Common.h +++ b/liblangutil/Common.h @@ -45,6 +45,11 @@ inline bool isIdentifierPart(char c) return isIdentifierStart(c) || isDecimalDigit(c); } +inline bool isIdentifierPartWithDot(char c) +{ + return isIdentifierPart(c) || c == '.'; +} + inline int hexValue(char c) { if (c >= '0' && c <= '9') diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index f532a73345b9..77f6f885f433 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -13,6 +13,7 @@ */ #include +#include #include #include #include @@ -46,6 +47,17 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer m_lineOffset(m_reader.lineNumber()), m_enforceViaYul(enforceViaYul) { + using namespace std::placeholders; + m_builtins + = {{"logs", + { + {"numLogs()", std::bind(&SemanticTest::numLogs, this, _1)}, + {"numLogTopics(uint256)", std::bind(&SemanticTest::numLogTopics, this, _1)}, + {"logTopic(uint256,uint256)", std::bind(&SemanticTest::logTopic, this, _1)}, + {"logAddress(uint256)", std::bind(&SemanticTest::logAddress, this, _1)}, + {"logData(uint256)", std::bind(&SemanticTest::logData, this, _1)}, + }}}; + string choice = m_reader.stringSetting("compileViaYul", "default"); if (choice == "also") { @@ -198,6 +210,15 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line bytes output; if (test.call().kind == FunctionCall::Kind::LowLevel) output = callLowLevel(test.call().arguments.rawBytes(), test.call().value.value); + else if (test.call().kind == FunctionCall::Kind::Builtin) + { + std::vector builtinPath; + boost::split(builtinPath, test.call().signature, boost::is_any_of(".")); + assert(builtinPath.size() == 2); + auto builtin = m_builtins[builtinPath.front()][builtinPath.back()]; + output = builtin(test.call()); + test.setFailure(output.empty()); + } else { soltestAssert( @@ -214,14 +235,23 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line } bool outputMismatch = (output != test.call().expectations.rawBytes()); - // Pre byzantium, it was not possible to return failure data, so we disregard - // output mismatch for those EVM versions. - if (test.call().expectations.failure && !m_transactionSuccessful && !m_evmVersion.supportsReturndata()) - outputMismatch = false; - if (m_transactionSuccessful != !test.call().expectations.failure || outputMismatch) - success = false; + if (test.call().kind == FunctionCall::Kind::Builtin) + { + if (outputMismatch) + success = false; + } + else + { + // Pre byzantium, it was not possible to return failure data, so we disregard + // output mismatch for those EVM versions. + if (test.call().expectations.failure && !m_transactionSuccessful && !m_evmVersion.supportsReturndata()) + outputMismatch = false; + + if (m_transactionSuccessful != !test.call().expectations.failure || outputMismatch) + success = false; + test.setFailure(!m_transactionSuccessful); + } - test.setFailure(!m_transactionSuccessful); test.setRawBytes(std::move(output)); test.setContractABI(m_compiler.contractABI(m_compiler.lastContractName())); } @@ -362,9 +392,73 @@ void SemanticTest::printUpdatedSettings(ostream& _stream, string const& _linePre _stream << _linePrefix << "// " << setting.first << ": " << setting.second << endl; } +bytes SemanticTest::numLogs(FunctionCall const&) +{ + // numLogs() + size_t result = SolidityExecutionFramework::numLogs(); + bytes r = util::toBigEndian(u256{result}); + return r; +} + +bytes SemanticTest::numLogTopics(FunctionCall const& call) +{ + // numLogTopics(uint256) + assert(call.arguments.parameters.size() == 1); + size_t logCount = SolidityExecutionFramework::numLogs(); + // todo: hex strings not supported by lexical_cast<..>(..) + auto logIdx = lexical_cast(call.arguments.parameters.front().rawString); + if (logCount > 0 && logIdx < logCount) + return util::toBigEndian(u256{SolidityExecutionFramework::numLogTopics(logIdx)}); + // empty result means failure. + return bytes{}; +} + +bytes SemanticTest::logTopic(FunctionCall const& call) +{ + // logTopic(uint256,uint256) + assert(call.arguments.parameters.size() == 2); + auto logIdx = lexical_cast(call.arguments.parameters.front().rawString); + auto topicIdx = lexical_cast(call.arguments.parameters.back().rawString); + size_t logCount = SolidityExecutionFramework::numLogs(); + // todo: hex strings not supported by lexical_cast<..>(..) + if (logCount > 0 && logIdx < logCount) + { + size_t topicCount = SolidityExecutionFramework::numLogTopics(logIdx); + if (topicCount > 0 && topicIdx < topicCount) + return util::toBigEndian(u256{SolidityExecutionFramework::logTopic(logIdx, topicIdx)}); + } + return{}; +} + +bytes SemanticTest::logAddress(FunctionCall const& call) +{ + // logAddress(uint256) + assert(call.arguments.parameters.size() == 1); + size_t logCount = SolidityExecutionFramework::numLogs(); + // todo: hex strings not supported by lexical_cast<..>(..) + auto logIdx = lexical_cast(call.arguments.parameters.front().rawString); + if (logCount > 0 && logIdx < logCount) + return util::toBigEndian(u256{u160{SolidityExecutionFramework::logAddress(logIdx)}}); + // empty result means failure. + return bytes{}; +} + +bytes SemanticTest::logData(FunctionCall const& call) +{ + // logData(uint256) + assert(call.arguments.parameters.size() == 1); + size_t logCount = SolidityExecutionFramework::numLogs(); + // todo: hex strings not supported by lexical_cast<..>(..) + auto logIdx = lexical_cast(call.arguments.parameters.front().rawString); + if (logCount > 0 && logIdx < logCount) + return SolidityExecutionFramework::logData(logIdx); + // empty result means failure. + return bytes{}; +} + void SemanticTest::parseExpectations(istream& _stream) { - TestFileParser parser{_stream}; + TestFileParser parser{_stream, &this->m_builtins}; auto functionCalls = parser.parseFunctionCalls(m_lineOffset); std::move(functionCalls.begin(), functionCalls.end(), back_inserter(m_tests)); } diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index acb656763aea..3664f7df4559 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -58,7 +58,15 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict /// Compiles and deploys currently held source. /// Returns true if deployment was successful, false otherwise. bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map const& _libraries = {}); + private: + // logs builtins. + bytes numLogs(FunctionCall const& call); + bytes numLogTopics(FunctionCall const& call); + bytes logTopic(FunctionCall const& call); + bytes logAddress(FunctionCall const& call); + bytes logData(FunctionCall const& call); + TestResult runTest(std::ostream& _stream, std::string const& _linePrefix, bool _formatted, bool _compileViaYul, bool _compileToEwasm); SourceMap m_sources; std::size_t m_lineOffset; @@ -70,6 +78,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict bool m_runWithABIEncoderV1Only = false; bool m_allowNonExistingFunctions = false; bool m_compileViaYulCanBeSet = false; + BuiltinFunctions m_builtins; }; } diff --git a/test/libsolidity/util/SoltestTypes.h b/test/libsolidity/util/SoltestTypes.h index e0f6caa1f26a..3f51fbb227e1 100644 --- a/test/libsolidity/util/SoltestTypes.h +++ b/test/libsolidity/util/SoltestTypes.h @@ -17,6 +17,8 @@ #include #include +#include + namespace solidity::frontend::test { @@ -286,7 +288,9 @@ struct FunctionCall /// Marks a library deployment call. Library, /// Check that the storage of the current contract is empty or non-empty. - Storage + Storage, + /// Call to a builtin. + Builtin }; Kind kind = Kind::Regular; /// Marks this function call as "short-handed", meaning @@ -294,4 +298,6 @@ struct FunctionCall bool omitsArrow = true; }; +using BuiltinFunctions = std::map>>; + } diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index 7654e384d1a1..ef42a3722751 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -107,6 +108,27 @@ vector TestFileParser::parseFunctionCall if (lowLevelCall) call.kind = FunctionCall::Kind::LowLevel; + // Treat all calls to functions containing '.' as calls to builtin functions. + if (call.signature.find('.') != std::string::npos) + { + assert(m_builtins != nullptr); + std::vector builtinPath; + boost::split(builtinPath, call.signature, boost::is_any_of(".")); + assert(builtinPath.size() == 2); + + auto module = m_builtins->find(builtinPath.front()); + if (module == m_builtins->end()) + throw TestParserError("builtin module '" + builtinPath.front() + "' not found"); + + auto builtin = module->second.find(builtinPath.back()); + if (builtin == module->second.end()) + throw TestParserError( + "builtin function '" + builtinPath.back() + "' not found in module '" + + builtinPath.front() + "'"); + + call.kind = FunctionCall::Kind::Builtin; + } + if (accept(Token::Comma, true)) call.value = parseFunctionCallValue(); @@ -602,7 +624,7 @@ string TestFileParser::Scanner::scanIdentifierOrKeyword() { string identifier; identifier += current(); - while (langutil::isIdentifierPart(peek())) + while (langutil::isIdentifierPartWithDot(peek())) { advance(); identifier += current(); diff --git a/test/libsolidity/util/TestFileParser.h b/test/libsolidity/util/TestFileParser.h index 63a95dd4a685..31e80878215b 100644 --- a/test/libsolidity/util/TestFileParser.h +++ b/test/libsolidity/util/TestFileParser.h @@ -52,7 +52,7 @@ class TestFileParser public: /// Constructor that takes an input stream \param _stream to operate on /// and creates the internal scanner. - TestFileParser(std::istream& _stream): m_scanner(_stream) {} + explicit TestFileParser(std::istream& _stream, BuiltinFunctions* _builtins = nullptr): m_scanner(_stream), m_builtins(_builtins) {} /// Parses function calls blockwise and returns a list of function calls found. /// Throws an exception if a function call cannot be parsed because of its @@ -183,6 +183,8 @@ class TestFileParser /// The current line number. Incremented when Token::Newline (//) is found and /// used to enhance parser error messages. size_t m_lineNumber = 0; + + BuiltinFunctions* m_builtins{nullptr}; }; } From aebb99030faa4bfb2709c4e4444a57e722a9861a Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Fri, 8 Jan 2021 14:41:09 -0500 Subject: [PATCH 2/7] [isoltest] Add simple event test. --- .../semanticTests/events/event_emit.sol | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 test/libsolidity/semanticTests/events/event_emit.sol diff --git a/test/libsolidity/semanticTests/events/event_emit.sol b/test/libsolidity/semanticTests/events/event_emit.sol new file mode 100644 index 000000000000..4d2a30d7f785 --- /dev/null +++ b/test/libsolidity/semanticTests/events/event_emit.sol @@ -0,0 +1,17 @@ +contract ClientReceipt { + event Deposit(address indexed _from, bytes32 indexed _id, uint _value); + function deposit(bytes32 _id) public payable { + emit Deposit(msg.sender, _id, msg.value); + } +} +// ==== +// compileViaYul: also +// ---- +// deposit(bytes32), 18 wei: 0x1234 -> +// logs.numLogs() -> 1 +// logs.logAddress(uint256): 0 -> 0x0fdd67305928fcac8d213d1e47bfa6165cd0b87b +// logs.logData(uint256): 0 -> 0x12 +// logs.numLogTopics(uint256): 0 -> 3 +// logs.logTopic(uint256,uint256): 0, 0 -> 0x19dacbf83c5de6658e14cbf7bcae5c15eca2eedecf1c66fbca928e4d351bea0f +// logs.logTopic(uint256,uint256): 0, 1 -> 0x1212121212121212121212121212120000000012 +// logs.logTopic(uint256,uint256): 0, 2 -> 0x1234 From 9eb87b00ab585e87964e52b9f92033157b91b067 Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Fri, 8 Jan 2021 19:08:23 -0500 Subject: [PATCH 3/7] [isoltest] Add support to use builtins within expectations. --- test/libsolidity/SemanticTest.cpp | 14 +++- .../semanticTests/builtins/smoke.sol | 10 +++ test/libsolidity/util/SoltestTypes.h | 8 +- test/libsolidity/util/TestFileParser.cpp | 78 ++++++++++++------- test/libsolidity/util/TestFileParser.h | 4 + test/libsolidity/util/TestFunctionCall.cpp | 27 +++++-- 6 files changed, 108 insertions(+), 33 deletions(-) create mode 100644 test/libsolidity/semanticTests/builtins/smoke.sol diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 77f6f885f433..5054612d7bfd 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -234,7 +234,19 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line ); } - bool outputMismatch = (output != test.call().expectations.rawBytes()); + bytes expectationOutput; + if (test.call().expectations.builtin) + { + std::vector builtinPath; + boost::split(builtinPath, test.call().expectations.builtin->signature, boost::is_any_of(".")); + assert(builtinPath.size() == 2); + auto builtin = m_builtins[builtinPath.front()][builtinPath.back()]; + expectationOutput = builtin(*test.call().expectations.builtin); + } + else + expectationOutput = test.call().expectations.rawBytes(); + + bool outputMismatch = (output != expectationOutput); if (test.call().kind == FunctionCall::Kind::Builtin) { if (outputMismatch) diff --git a/test/libsolidity/semanticTests/builtins/smoke.sol b/test/libsolidity/semanticTests/builtins/smoke.sol new file mode 100644 index 000000000000..4070e6133245 --- /dev/null +++ b/test/libsolidity/semanticTests/builtins/smoke.sol @@ -0,0 +1,10 @@ +contract ClientReceipt { +} +// ==== +// compileViaYul: also +// ---- +// logs.numLogs() -> logs.numLogs() +// logs.logAddress(uint256): 0 -> logs.logAddress(uint256): 0 +// logs.logData(uint256): 0 -> logs.logData(uint256): 0 +// logs.numLogTopics(uint256): 0 -> logs.numLogTopics(uint256): 0 +// logs.logTopic(uint256,uint256): 0, 0 -> logs.logTopic(uint256,uint256): 0, 0 diff --git a/test/libsolidity/util/SoltestTypes.h b/test/libsolidity/util/SoltestTypes.h index 3f51fbb227e1..a3cd1cb3a864 100644 --- a/test/libsolidity/util/SoltestTypes.h +++ b/test/libsolidity/util/SoltestTypes.h @@ -176,6 +176,8 @@ struct Parameter }; using ParameterList = std::vector; +struct FunctionCall; + /** * 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 @@ -195,6 +197,9 @@ struct FunctionCallExpectations /// A Comment that can be attached to the expectations, /// that is retained and can be displayed. std::string comment; + /// An expectation can also be defined by a builtin function. + std::unique_ptr builtin; + /// 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 @@ -298,6 +303,7 @@ struct FunctionCall bool omitsArrow = true; }; -using BuiltinFunctions = std::map>>; +using BuiltinFunction = std::function; +using BuiltinFunctions = std::map>; } diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index ef42a3722751..071e5c43ee1e 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -111,21 +111,7 @@ vector TestFileParser::parseFunctionCall // Treat all calls to functions containing '.' as calls to builtin functions. if (call.signature.find('.') != std::string::npos) { - assert(m_builtins != nullptr); - std::vector builtinPath; - boost::split(builtinPath, call.signature, boost::is_any_of(".")); - assert(builtinPath.size() == 2); - - auto module = m_builtins->find(builtinPath.front()); - if (module == m_builtins->end()) - throw TestParserError("builtin module '" + builtinPath.front() + "' not found"); - - auto builtin = module->second.find(builtinPath.back()); - if (builtin == module->second.end()) - throw TestParserError( - "builtin function '" + builtinPath.back() + "' not found in module '" - + builtinPath.front() + "'"); - + checkBuiltinFunction(call.signature); call.kind = FunctionCall::Kind::Builtin; } @@ -180,6 +166,24 @@ vector TestFileParser::parseFunctionCall return calls; } +void TestFileParser::checkBuiltinFunction(std::string const& signature) +{ + assert(m_builtins != nullptr); + vector builtinPath; + boost::split(builtinPath, signature, boost::is_any_of(".")); + assert(builtinPath.size() == 2); + + auto module = m_builtins->find(builtinPath.front()); + if (module == m_builtins->end()) + throw TestParserError("builtin module '" + builtinPath.front() + "' not found"); + + auto builtin = module->second.find(builtinPath.back()); + if (builtin == module->second.end()) + throw TestParserError( + "builtin function '" + builtinPath.back() + "' not found in module '" + + builtinPath.front() + "'"); +} + bool TestFileParser::accept(Token _token, bool const _expect) { if (m_scanner.currentToken() != _token) @@ -279,21 +283,43 @@ FunctionCallExpectations TestFileParser::parseFunctionCallExpectations() { FunctionCallExpectations expectations; - auto param = parseParameter(); - if (param.abiType.type == ABIType::None) + if (accept(Token::Identifier, false)) { - expectations.failure = false; - return expectations; + string signature; + FunctionCallArgs arguments; + + signature = parseFunctionSignature().first; + if (signature.find('.') == std::string::npos) + throw TestParserError("Only builtins can be called as an expectation."); + + checkBuiltinFunction(signature); + + if (accept(Token::Colon, true)) + arguments = parseFunctionCallArguments(); + + expectations.builtin = std::make_unique(); + expectations.builtin->arguments = arguments; + expectations.builtin->signature = signature; + expectations.builtin->kind = FunctionCall::Kind::Builtin; } - expectations.result.emplace_back(param); + else + { + auto param = parseParameter(); + if (param.abiType.type == ABIType::None) + { + expectations.failure = false; + return expectations; + } + expectations.result.emplace_back(param); - while (accept(Token::Comma, true)) - expectations.result.emplace_back(parseParameter()); + while (accept(Token::Comma, true)) + expectations.result.emplace_back(parseParameter()); - /// We have always one virtual parameter in the parameter list. - /// If its type is FAILURE, the expected result is also a REVERT etc. - if (expectations.result.at(0).abiType.type != ABIType::Failure) - expectations.failure = false; + /// We have always one virtual parameter in the parameter list. + /// If its type is FAILURE, the expected result is also a REVERT etc. + if (expectations.result.at(0).abiType.type != ABIType::Failure) + expectations.failure = false; + } return expectations; } diff --git a/test/libsolidity/util/TestFileParser.h b/test/libsolidity/util/TestFileParser.h index 31e80878215b..9693dc50807e 100644 --- a/test/libsolidity/util/TestFileParser.h +++ b/test/libsolidity/util/TestFileParser.h @@ -177,6 +177,10 @@ class TestFileParser /// Parses the current string literal. std::string parseString(); + /// Checks whether a builtin function with the given signature exist. + /// This function will throw an TestParserError exception, if not. + void checkBuiltinFunction(std::string const& signature); + /// A scanner instance Scanner m_scanner; diff --git a/test/libsolidity/util/TestFunctionCall.cpp b/test/libsolidity/util/TestFunctionCall.cpp index e13fccf0916d..dd8d73cf547a 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -124,20 +124,37 @@ string TestFunctionCall::format( /// Format either the expected output or the actual result output string result; + auto& builtin = m_call.expectations.builtin; if (!_renderResult) { - bool const isFailure = m_call.expectations.failure; - result = isFailure ? - formatFailure(_errorReporter, m_call, m_rawBytes, _renderResult, highlight) : - formatRawParameters(m_call.expectations.result); + if (builtin) + { + result = builtin->signature; + if (!builtin->arguments.parameters.empty()) + result += ": "; + result += formatRawParameters(builtin->arguments.parameters); + } + else + { + bool const isFailure = m_call.expectations.failure; + result = isFailure ? + formatFailure(_errorReporter, m_call, m_rawBytes, _renderResult, highlight) : + formatRawParameters(m_call.expectations.result); + } if (!result.empty()) - AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << ws << result; + { + AnsiColorized(stream, false, {util::formatting::RESET}) << ws; + AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << result; + } } else { if (m_calledNonExistingFunction) _errorReporter.warning("The function \"" + m_call.signature + "\" is not known to the compiler."); + if (builtin) + _errorReporter.warning("The expectation \"" + builtin->signature + ": " + formatRawParameters(builtin->arguments.parameters) + "\" will be replaced with the actual value returned by the test."); + bytes output = m_rawBytes; bool const isFailure = m_failure; result = isFailure ? From 54ab9907c5c3fa3f91cc731f9d94f935515ca1ed Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Fri, 8 Jan 2021 19:43:46 -0500 Subject: [PATCH 4/7] Fix Tests. --- test/libsolidity/util/SoltestTypes.h | 2 +- .../util/TestFunctionCallTests.cpp | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/libsolidity/util/SoltestTypes.h b/test/libsolidity/util/SoltestTypes.h index a3cd1cb3a864..dbe7dd957017 100644 --- a/test/libsolidity/util/SoltestTypes.h +++ b/test/libsolidity/util/SoltestTypes.h @@ -198,7 +198,7 @@ struct FunctionCallExpectations /// that is retained and can be displayed. std::string comment; /// An expectation can also be defined by a builtin function. - std::unique_ptr builtin; + std::shared_ptr builtin; /// ABI encoded `bytes` of parsed expected return values. It is checked /// against the actual result of a function call when used in test framework. diff --git a/test/libsolidity/util/TestFunctionCallTests.cpp b/test/libsolidity/util/TestFunctionCallTests.cpp index dc3030384f54..cdda77dde40d 100644 --- a/test/libsolidity/util/TestFunctionCallTests.cpp +++ b/test/libsolidity/util/TestFunctionCallTests.cpp @@ -39,7 +39,7 @@ BOOST_AUTO_TEST_CASE(format_unsigned_singleline) bytes expectedBytes = toBigEndian(u256{1}); ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(uint8)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(format_unsigned_singleline_signed_encoding) bytes expectedBytes = toBigEndian(u256{1}); ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(uint8)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -79,7 +79,7 @@ BOOST_AUTO_TEST_CASE(format_unsigned_multiline) bytes expectedBytes = toBigEndian(u256{1}); ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; Parameter result{expectedBytes, "1", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{result}, false, string{}}; + FunctionCallExpectations expectations{vector{result}, false, string{}, {}}; FunctionCallArgs arguments{vector{}, string{}}; FunctionCall call{"f(uint8)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -94,7 +94,7 @@ BOOST_AUTO_TEST_CASE(format_multiple_unsigned_singleline) 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{}}; + FunctionCallExpectations expectations{vector{param, param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param, param}, string{}}; FunctionCall call{"f(uint8, uint8)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(format_signed_singleline) bytes expectedBytes = toBigEndian(u256{-1}); ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "-1", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(int8)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(format_hex_singleline) bytes expectedBytes = result + bytes(32 - result.size(), 0); ABIType abiType{ABIType::Hex, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "0x31", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(bytes32)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -150,7 +150,7 @@ BOOST_AUTO_TEST_CASE(format_hex_string_singleline) bytes expectedBytes = fromHex("4200ef"); ABIType abiType{ABIType::HexString, ABIType::AlignLeft, 3}; Parameter param{expectedBytes, "hex\"4200ef\"", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(string)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -164,7 +164,7 @@ BOOST_AUTO_TEST_CASE(format_bool_true_singleline) bytes expectedBytes = toBigEndian(u256{true}); ABIType abiType{ABIType::Boolean, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "true", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(bool)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -185,7 +185,7 @@ BOOST_AUTO_TEST_CASE(format_bool_false_singleline) bytes expectedBytes = toBigEndian(u256{false}); ABIType abiType{ABIType::Boolean, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "false", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(bool)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -199,7 +199,7 @@ BOOST_AUTO_TEST_CASE(format_bool_left_singleline) bytes expectedBytes = toBigEndian(u256{false}); ABIType abiType{ABIType::Boolean, ABIType::AlignLeft, 32}; Parameter param{expectedBytes, "left(false)", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(bool)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -214,7 +214,7 @@ BOOST_AUTO_TEST_CASE(format_hex_number_right_singleline) bytes expectedBytes = result + bytes(32 - result.size(), 0); ABIType abiType{ABIType::Hex, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "right(0x42)", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(bool)", {0}, arguments, expectations}; call.omitsArrow = false; @@ -228,7 +228,7 @@ BOOST_AUTO_TEST_CASE(format_empty_byte_range) bytes expectedBytes; ABIType abiType{ABIType::None, ABIType::AlignNone, 0}; Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{param}, false, string{}}; + FunctionCallExpectations expectations{vector{param}, false, string{}, {}}; FunctionCallArgs arguments{vector{}, string{}}; FunctionCall call{"f()", {0}, arguments, expectations}; call.omitsArrow = false; @@ -242,7 +242,7 @@ BOOST_AUTO_TEST_CASE(format_failure_singleline) bytes expectedBytes = toBigEndian(u256{1}); ABIType abiType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; Parameter param{expectedBytes, "1", abiType, FormatInfo{}}; - FunctionCallExpectations expectations{vector{}, true, string{}}; + FunctionCallExpectations expectations{vector{}, true, string{}, {}}; FunctionCallArgs arguments{vector{param}, string{}}; FunctionCall call{"f(uint8)", {0}, arguments, expectations}; call.omitsArrow = false; From 38470731b5f0b72b477f3d37d11261a3f39460f4 Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Mon, 11 Jan 2021 21:18:19 -0500 Subject: [PATCH 5/7] Basic tracking of Event producer / consumer. --- test/ExecutionFramework.h | 35 ++++++++ test/libsolidity/SemanticTest.cpp | 88 +++++++++++++++++++ test/libsolidity/SemanticTest.h | 8 ++ .../semanticTests/events/event_emit.sol | 28 +++--- test/libsolidity/util/TestFunctionCall.h | 28 ++++++ 5 files changed, 177 insertions(+), 10 deletions(-) diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 1b320776e12a..2b99161faff9 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -51,6 +51,25 @@ class ExecutionFramework { public: + /// LOG record. + struct log_record + { + /// The address of the account which created the log. + util::h160 creator; + + /// The data attached to the log. + bytes data; + + /// The log topics. + std::vector topics; + + /// Equal operator. + bool operator==(const log_record& other) const noexcept + { + return creator == other.creator && data == other.data && topics == other.topics; + } + }; + ExecutionFramework(); ExecutionFramework(langutil::EVMVersion _evmVersion, std::vector const& _vmPaths); virtual ~ExecutionFramework() = default; @@ -277,6 +296,22 @@ class ExecutionFramework util::h160 logAddress(size_t _logIdx) const; bytes logData(size_t _logIdx) const; + std::vector recordedLogs() { + std::vector logs{}; + for (size_t logIdx = 0; logIdx < numLogs(); ++logIdx) + { + log_record record; + const auto& data = m_evmcHost->recorded_logs.at(logIdx).data; + record.data = bytes{data.begin(), data.end()}; + record.creator = EVMHost::convertFromEVMC(m_evmcHost->recorded_logs.at(logIdx).creator); + for (size_t topicIdx = 0; topicIdx < numLogTopics(logIdx); ++topicIdx) { + record.topics.emplace_back(EVMHost::convertFromEVMC(m_evmcHost->recorded_logs.at(logIdx).topics.at(topicIdx))); + } + logs.emplace_back(record); + } + return logs; + } + langutil::EVMVersion m_evmVersion; solidity::frontend::RevertStrings m_revertStrings = solidity::frontend::RevertStrings::Default; solidity::frontend::OptimiserSettings m_optimiserSettings = solidity::frontend::OptimiserSettings::minimal(); diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 5054612d7bfd..1727bd09d291 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -56,6 +56,7 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer {"logTopic(uint256,uint256)", std::bind(&SemanticTest::logTopic, this, _1)}, {"logAddress(uint256)", std::bind(&SemanticTest::logAddress, this, _1)}, {"logData(uint256)", std::bind(&SemanticTest::logData, this, _1)}, + {"expectEvent(uint256,string)", std::bind(&SemanticTest::expectEvent, this, _1)}, }}}; string choice = m_reader.stringSetting("compileViaYul", "default"); @@ -142,6 +143,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line selectVM(evmc_capabilities::EVMC_CAPABILITY_EVM1); reset(); + m_touchedLogs.clear(); m_compileViaYul = _compileViaYul; if (_compileToEwasm) @@ -162,6 +164,14 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line bool constructed = false; + // Iterate through the test calls and set the previous call. + TestFunctionCall* previousCall{nullptr}; + for (auto& test: m_tests) + { + test.setPreviousCall(previousCall); + previousCall = &test; + } + for (auto& test: m_tests) { if (constructed) @@ -261,6 +271,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line if (m_transactionSuccessful != !test.call().expectations.failure || outputMismatch) success = false; + success = checkLogs(test); test.setFailure(!m_transactionSuccessful); } @@ -269,6 +280,10 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line } } + TestFunctionCall fakeLastTestFunctionCall(FunctionCall{}); + fakeLastTestFunctionCall.setPreviousCall(previousCall); + success = checkLogs(fakeLastTestFunctionCall); + if (success && !m_runWithYul && _compileViaYul) { m_compileViaYulCanBeSet = true; @@ -404,6 +419,43 @@ void SemanticTest::printUpdatedSettings(ostream& _stream, string const& _linePre _stream << _linePrefix << "// " << setting.first << ": " << setting.second << endl; } +bool SemanticTest::checkLogs(TestFunctionCall& _call) +{ + _call.setLogs(SolidityExecutionFramework::recordedLogs()); + + TestFunctionCall* producer = _call.previousCall(); + std::vector consumers{}; + // Only non-builtins are able to produce logs. + // So lets search from the current call up to the first non-builtin. + while (producer != nullptr && producer->call().kind == FunctionCall::Kind::Builtin) + if (producer->previousCall() != nullptr) + { + // On the way up to the producer we track all builtins that where on the way. + // Only builtins can consume logs, we store them in the consumers vector. + consumers.emplace_back(&producer->call()); + producer = producer->previousCall(); + } + + // Producer will now point to the call that probably produced a log. + if (producer) + { + // We iterate through the consumers to find out what logs they have consumed. + for (auto& consumer: consumers) + for (auto logIdx: m_touchedLogs[consumer]) + // All logs that where touched by the consumer, will be marked as + // touched within the producer. + touchLog(producer->call(), logIdx); + + // Finally we update the consumed logs within the producer. + for (auto& logIdx: m_touchedLogs[&producer->call()]) + producer->consumedLogs().insert(logIdx); + + std::cout << producer->consumedLogs().size() << " / " << producer->logs().size() << std::endl; + return producer->consumedLogs().size() == producer->logs().size(); + } + return true; +} + bytes SemanticTest::numLogs(FunctionCall const&) { // numLogs() @@ -419,6 +471,7 @@ bytes SemanticTest::numLogTopics(FunctionCall const& call) size_t logCount = SolidityExecutionFramework::numLogs(); // todo: hex strings not supported by lexical_cast<..>(..) auto logIdx = lexical_cast(call.arguments.parameters.front().rawString); + touchLog(call, logIdx); if (logCount > 0 && logIdx < logCount) return util::toBigEndian(u256{SolidityExecutionFramework::numLogTopics(logIdx)}); // empty result means failure. @@ -430,6 +483,7 @@ bytes SemanticTest::logTopic(FunctionCall const& call) // logTopic(uint256,uint256) assert(call.arguments.parameters.size() == 2); auto logIdx = lexical_cast(call.arguments.parameters.front().rawString); + touchLog(call, logIdx); auto topicIdx = lexical_cast(call.arguments.parameters.back().rawString); size_t logCount = SolidityExecutionFramework::numLogs(); // todo: hex strings not supported by lexical_cast<..>(..) @@ -449,6 +503,7 @@ bytes SemanticTest::logAddress(FunctionCall const& call) size_t logCount = SolidityExecutionFramework::numLogs(); // todo: hex strings not supported by lexical_cast<..>(..) auto logIdx = lexical_cast(call.arguments.parameters.front().rawString); + touchLog(call, logIdx); if (logCount > 0 && logIdx < logCount) return util::toBigEndian(u256{u160{SolidityExecutionFramework::logAddress(logIdx)}}); // empty result means failure. @@ -462,12 +517,45 @@ bytes SemanticTest::logData(FunctionCall const& call) size_t logCount = SolidityExecutionFramework::numLogs(); // todo: hex strings not supported by lexical_cast<..>(..) auto logIdx = lexical_cast(call.arguments.parameters.front().rawString); + touchLog(call, logIdx); if (logCount > 0 && logIdx < logCount) return SolidityExecutionFramework::logData(logIdx); // empty result means failure. return bytes{}; } +bytes SemanticTest::expectEvent(FunctionCall const& call) +{ + // expectEvent(uint256,string): logIdx, eventSignature + assert(call.arguments.parameters.size() == 2); + size_t logCount = SolidityExecutionFramework::numLogs(); + // todo: hex strings not supported by lexical_cast<..>(..) + auto logIdx = lexical_cast(call.arguments.parameters.front().rawString); + touchLog(call, logIdx); + auto logSignature = call.arguments.parameters.back().rawString; + assert(logSignature.length() >= 2); + logSignature = logSignature.substr(1, logSignature.length() - 2); + h256 logSignatureHash{util::keccak256(logSignature)}; + if (logCount > 0 && logIdx < logCount) + { + vector topics; + size_t topicCount = SolidityExecutionFramework::numLogTopics(logIdx); + for (size_t topicIdx = 0; topicIdx < topicCount; ++topicIdx) + topics.push_back(SolidityExecutionFramework::logTopic(logIdx, topicIdx)); + // remove topics[0], if the signature matches. + if (!topics.empty() && topics[0] == logSignatureHash) + topics.erase(topics.begin()); + bytes result; + for (auto& topic : topics) + result += util::toBigEndian(topic); + result += SolidityExecutionFramework::logData(logIdx); + // todo: anonymous events with no data would be treated as error, maybe not that important. + return result; + } + // empty result means failure. + return bytes{}; +} + void SemanticTest::parseExpectations(istream& _stream) { TestFileParser parser{_stream, &this->m_builtins}; diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index 3664f7df4559..dd22063df011 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -60,12 +60,19 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map const& _libraries = {}); private: + bool checkLogs(TestFunctionCall& _call); + // logs builtins. bytes numLogs(FunctionCall const& call); bytes numLogTopics(FunctionCall const& call); bytes logTopic(FunctionCall const& call); bytes logAddress(FunctionCall const& call); bytes logData(FunctionCall const& call); + bytes expectEvent(FunctionCall const& call); + void touchLog(FunctionCall const& call, size_t _logIdx) + { + m_touchedLogs[&call].insert(_logIdx); + } TestResult runTest(std::ostream& _stream, std::string const& _linePrefix, bool _formatted, bool _compileViaYul, bool _compileToEwasm); SourceMap m_sources; @@ -79,6 +86,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict bool m_allowNonExistingFunctions = false; bool m_compileViaYulCanBeSet = false; BuiltinFunctions m_builtins; + std::map> m_touchedLogs; }; } diff --git a/test/libsolidity/semanticTests/events/event_emit.sol b/test/libsolidity/semanticTests/events/event_emit.sol index 4d2a30d7f785..f9241007d903 100644 --- a/test/libsolidity/semanticTests/events/event_emit.sol +++ b/test/libsolidity/semanticTests/events/event_emit.sol @@ -1,17 +1,25 @@ contract ClientReceipt { - event Deposit(address indexed _from, bytes32 indexed _id, uint _value); + event D(address indexed _from, bytes32 indexed _id, uint _value); + event D2(address indexed _from, bytes32 indexed _id, uint _value) anonymous; + event D3(address _from, bytes32 indexed _id, uint _value); function deposit(bytes32 _id) public payable { - emit Deposit(msg.sender, _id, msg.value); + emit D(msg.sender, _id, msg.value); + emit D2(msg.sender, _id, msg.value); + emit D3(msg.sender, _id, msg.value); + } + function deposit2(bytes32 _id) public payable { + emit D(msg.sender, _id, msg.value); } } + +// logs.expectEvent(uint256,string): 0, "D(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 18 + +// deposit2(bytes32), 18 wei: 0x1234 -> +// deposit(bytes32), 18 wei: 0x1234 -> // ==== -// compileViaYul: also +// compileViaYul: false // ---- // deposit(bytes32), 18 wei: 0x1234 -> -// logs.numLogs() -> 1 -// logs.logAddress(uint256): 0 -> 0x0fdd67305928fcac8d213d1e47bfa6165cd0b87b -// logs.logData(uint256): 0 -> 0x12 -// logs.numLogTopics(uint256): 0 -> 3 -// logs.logTopic(uint256,uint256): 0, 0 -> 0x19dacbf83c5de6658e14cbf7bcae5c15eca2eedecf1c66fbca928e4d351bea0f -// logs.logTopic(uint256,uint256): 0, 1 -> 0x1212121212121212121212121212120000000012 -// logs.logTopic(uint256,uint256): 0, 2 -> 0x1234 +// logs.expectEvent(uint256,string): 0, "D(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 18 +// logs.expectEvent(uint256,string): 1, "D2(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 18 +// logs.expectEvent(uint256,string): 2, "D3(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x12 diff --git a/test/libsolidity/util/TestFunctionCall.h b/test/libsolidity/util/TestFunctionCall.h index 7daac093acd1..416949cbcbec 100644 --- a/test/libsolidity/util/TestFunctionCall.h +++ b/test/libsolidity/util/TestFunctionCall.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include namespace solidity::frontend::test @@ -80,6 +82,26 @@ class TestFunctionCall 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); } + std::vector logs() + { + return m_rawLogs; + } + void setLogs(std::vector _logs) + { + m_rawLogs = std::move(_logs); + } + std::set& consumedLogs() + { + return m_consumedLogs; + } + void setPreviousCall(TestFunctionCall* _call) + { + m_previousCall = _call; + } + TestFunctionCall* previousCall() + { + return m_previousCall; + } private: /// Tries to format the given `bytes`, applying the detected ABI types that have be set for each parameter. @@ -124,6 +146,10 @@ class TestFunctionCall FunctionCall m_call; /// Result of the actual call been made. bytes m_rawBytes = bytes{}; + /// Logs created by the actual call. + std::vector m_rawLogs{}; + std::set m_consumedLogs; + /// 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 @@ -131,6 +157,8 @@ class TestFunctionCall Json::Value m_contractABI; /// Flags that the test failed because the called function is not known to exist on the contract. bool m_calledNonExistingFunction = false; + + TestFunctionCall* m_previousCall = nullptr; }; } From d6375bd4557c93770bdd87c0cf42eae85bbd841c Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Tue, 12 Jan 2021 19:41:33 -0500 Subject: [PATCH 6/7] Basic expectation generation, if events where not consumed. --- test/ExecutionFramework.h | 3 +- test/libsolidity/SemanticTest.cpp | 10 +- .../semanticTests/events/event_emit.sol | 25 ++--- test/libsolidity/util/TestFunctionCall.cpp | 101 +++++++++++++++++- test/libsolidity/util/TestFunctionCall.h | 22 +++- 5 files changed, 135 insertions(+), 26 deletions(-) diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 2b99161faff9..81459a0a47cf 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -51,9 +51,9 @@ class ExecutionFramework { public: - /// LOG record. struct log_record { + size_t index; /// The address of the account which created the log. util::h160 creator; @@ -302,6 +302,7 @@ class ExecutionFramework { log_record record; const auto& data = m_evmcHost->recorded_logs.at(logIdx).data; + record.index = logIdx; record.data = bytes{data.begin(), data.end()}; record.creator = EVMHost::convertFromEVMC(m_evmcHost->recorded_logs.at(logIdx).creator); for (size_t topicIdx = 0; topicIdx < numLogTopics(logIdx); ++topicIdx) { diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 1727bd09d291..ae3f9e7be16b 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -282,7 +282,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line TestFunctionCall fakeLastTestFunctionCall(FunctionCall{}); fakeLastTestFunctionCall.setPreviousCall(previousCall); - success = checkLogs(fakeLastTestFunctionCall); + success &= checkLogs(fakeLastTestFunctionCall); if (success && !m_runWithYul && _compileViaYul) { @@ -428,7 +428,9 @@ bool SemanticTest::checkLogs(TestFunctionCall& _call) // Only non-builtins are able to produce logs. // So lets search from the current call up to the first non-builtin. while (producer != nullptr && producer->call().kind == FunctionCall::Kind::Builtin) - if (producer->previousCall() != nullptr) + if (producer->previousCall() == nullptr) + break; + else { // On the way up to the producer we track all builtins that where on the way. // Only builtins can consume logs, we store them in the consumers vector. @@ -450,8 +452,8 @@ bool SemanticTest::checkLogs(TestFunctionCall& _call) for (auto& logIdx: m_touchedLogs[&producer->call()]) producer->consumedLogs().insert(logIdx); - std::cout << producer->consumedLogs().size() << " / " << producer->logs().size() << std::endl; - return producer->consumedLogs().size() == producer->logs().size(); + // It is ok if some builtins consumed log events. + return producer->consumedLogs().size() >= producer->logs().size(); } return true; } diff --git a/test/libsolidity/semanticTests/events/event_emit.sol b/test/libsolidity/semanticTests/events/event_emit.sol index f9241007d903..e71254151515 100644 --- a/test/libsolidity/semanticTests/events/event_emit.sol +++ b/test/libsolidity/semanticTests/events/event_emit.sol @@ -1,25 +1,18 @@ contract ClientReceipt { - event D(address indexed _from, bytes32 indexed _id, uint _value); - event D2(address indexed _from, bytes32 indexed _id, uint _value) anonymous; - event D3(address _from, bytes32 indexed _id, uint _value); + event A(address _from, bytes32 _id, uint _value); + event B(address _from, bytes32 _id, uint indexed _value) anonymous; + event C(address _from, bytes32 indexed _id, uint _value); function deposit(bytes32 _id) public payable { - emit D(msg.sender, _id, msg.value); - emit D2(msg.sender, _id, msg.value); - emit D3(msg.sender, _id, msg.value); - } - function deposit2(bytes32 _id) public payable { - emit D(msg.sender, _id, msg.value); + emit A(msg.sender, _id, msg.value); + emit B(msg.sender, _id, msg.value); + emit C(msg.sender, _id, msg.value); } } -// logs.expectEvent(uint256,string): 0, "D(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 18 - -// deposit2(bytes32), 18 wei: 0x1234 -> -// deposit(bytes32), 18 wei: 0x1234 -> // ==== // compileViaYul: false // ---- // deposit(bytes32), 18 wei: 0x1234 -> -// logs.expectEvent(uint256,string): 0, "D(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 18 -// logs.expectEvent(uint256,string): 1, "D2(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 18 -// logs.expectEvent(uint256,string): 2, "D3(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x12 +// logs.expectEvent(uint256,string): 0, "A(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 0x12 +// logs.expectEvent(uint256,string): 1, "" -> 0x12, 0x1212121212121212121212121212120000000012, 0x1234 +// logs.expectEvent(uint256,string): 2, "C(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x12 diff --git a/test/libsolidity/util/TestFunctionCall.cpp b/test/libsolidity/util/TestFunctionCall.cpp index dd8d73cf547a..1911e4e420e9 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -18,7 +18,9 @@ #include #include +#include +#include #include #include @@ -32,6 +34,46 @@ using namespace std; using Token = soltest::Token; +TestFunctionCall::EventInformation +TestFunctionCall::findEventSignature(solidity::test::ExecutionFramework::log_record const& log) const +{ + for (const auto& entity: m_contractABI) + { + EventInformation eventInfo; + if (entity["type"].isString() && entity["type"].asString() == "event") + { + string name{entity["name"].asString()}; + bool anonymous{entity["anonymous"].asBool()}; + vector argumentTypes; + for (const auto& input: entity["inputs"]) + { + string type{input["internalType"].asString()}; + argumentTypes.emplace_back(type); + if (input["indexed"].asBool()) + eventInfo.indexedTypes.push_back(type); + else + eventInfo.nonIndexedTypes.push_back(type); + } + if (!name.empty() && !anonymous) + { + std::string signature{name}; + signature += "("; + for (auto& arg: argumentTypes) + signature += arg + ","; + if (!argumentTypes.empty()) + signature = signature.substr(0, signature.length() - 1); + signature += ")"; + if (!log.topics.empty() && log.topics[0] == util::keccak256(signature)) + { + eventInfo.signature = signature; + return eventInfo; + } + } + } + } + return {}; +} + string TestFunctionCall::format( ErrorReporter& _errorReporter, string const& _linePrefix, @@ -41,7 +83,7 @@ string TestFunctionCall::format( { stringstream stream; - bool highlight = !matchesExpectation() && _highlight; + bool highlight = (!matchesExpectation() || hasUnconsumedLogs()) && _highlight; auto formatOutput = [&](bool const _singleLine) { @@ -93,7 +135,6 @@ string TestFunctionCall::format( if (!m_call.arguments.parameters.at(0).format.newline) stream << ws; stream << output; - } /// Formats comments on the function parameters and the arrow taking @@ -116,8 +157,8 @@ string TestFunctionCall::format( stream << endl << _linePrefix << newline << ws; if (!m_call.arguments.comment.empty()) { - stream << comment << m_call.arguments.comment << comment; - stream << endl << _linePrefix << newline << ws; + stream << comment << m_call.arguments.comment << comment; + stream << endl << _linePrefix << newline << ws; } stream << arrow; } @@ -125,6 +166,51 @@ string TestFunctionCall::format( /// Format either the expected output or the actual result output string result; auto& builtin = m_call.expectations.builtin; + if (hasUnconsumedLogs()) + { + for (auto& log: unconsumedLogs()) + { + EventInformation eventInfo = findEventSignature(log); + string signature{eventInfo.signature}; + stream << std::endl + << "// logs.expectEvent(uint256,string): " << log.index << ", " + << "\"" + signature + "\" -> "; + + vector topicsAndData; + for (auto& topic: log.topics) + if ((topic != *log.topics.begin() && !signature.empty()) || signature.empty()) + topicsAndData.emplace_back(u256{topic}); + + assert(log.data.size() % 32 == 0); + + if (eventInfo.signature.empty()) + { + for (long current = 0; current < static_cast(log.data.size() / 32); ++current) + { + bytes parameter{log.data.begin() + current * 32, log.data.begin() + current * 32 + 32}; + topicsAndData.emplace_back(util::fromBigEndian(parameter)); + } + } + else + { + long current = 0; + for (auto& type: eventInfo.nonIndexedTypes) + { + // todo: use type information to improve decoding. + (void) type; + bytes parameter{log.data.begin() + current, log.data.begin() + current + 32}; + current += 32; + topicsAndData.emplace_back(util::fromBigEndian(parameter)); + } + } + for (auto& element: topicsAndData) + { + stream << util::toCompactHexWithPrefix(element); + if (&element != &*topicsAndData.rbegin()) + stream << ", "; + } + } + } if (!_renderResult) { if (builtin) @@ -152,6 +238,13 @@ string TestFunctionCall::format( if (m_calledNonExistingFunction) _errorReporter.warning("The function \"" + m_call.signature + "\" is not known to the compiler."); + if (m_consumedLogs.size() != m_rawLogs.size()) + { + stringstream str; + str << "The function \"" << m_call.signature << "\" produced " << m_rawLogs.size() <<" log(s), but only " << m_consumedLogs.size() << " log(s) where consumed."; + _errorReporter.error(str.str()); + } + if (builtin) _errorReporter.warning("The expectation \"" + builtin->signature + ": " + formatRawParameters(builtin->arguments.parameters) + "\" will be replaced with the actual value returned by the test."); diff --git a/test/libsolidity/util/TestFunctionCall.h b/test/libsolidity/util/TestFunctionCall.h index 416949cbcbec..56f0b97b61e4 100644 --- a/test/libsolidity/util/TestFunctionCall.h +++ b/test/libsolidity/util/TestFunctionCall.h @@ -82,7 +82,7 @@ class TestFunctionCall 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); } - std::vector logs() + std::vector logs() const { return m_rawLogs; } @@ -94,6 +94,18 @@ class TestFunctionCall { return m_consumedLogs; } + bool hasUnconsumedLogs() const + { + return m_consumedLogs.size() < m_rawLogs.size(); + } + std::vector unconsumedLogs() const + { + std::vector result; + for (auto& log: m_rawLogs) + if (m_consumedLogs.find(log.index) == m_consumedLogs.end()) + result.emplace_back(m_rawLogs[log.index]); + return result; + } void setPreviousCall(TestFunctionCall* _call) { m_previousCall = _call; @@ -104,6 +116,14 @@ class TestFunctionCall } private: + struct EventInformation + { + std::string signature; + std::vector indexedTypes; + std::vector nonIndexedTypes; + }; + EventInformation findEventSignature(solidity::test::ExecutionFramework::log_record const& log) const; + /// Tries to format the given `bytes`, applying the detected ABI types that have be set for each parameter. /// Throws if there's a mismatch in the size of `bytes` and the desired formats that are specified /// in the ABI type. From a8c179d651e0e3bda507beaf6b5e284e04bef15f Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Mon, 18 Jan 2021 20:00:23 -0500 Subject: [PATCH 7/7] Minor refactoring & correction of test generation. --- test/libsolidity/SemanticTest.cpp | 9 +- .../semanticTests/events/event_emit.sol | 16 +- test/libsolidity/util/TestFunctionCall.cpp | 383 +++++++++--------- test/libsolidity/util/TestFunctionCall.h | 31 +- 4 files changed, 242 insertions(+), 197 deletions(-) diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index ae3f9e7be16b..8db0ae445045 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -271,7 +271,10 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line if (m_transactionSuccessful != !test.call().expectations.failure || outputMismatch) success = false; - success = checkLogs(test); + + if (success) + success = checkLogs(test); + test.setFailure(!m_transactionSuccessful); } @@ -450,10 +453,10 @@ bool SemanticTest::checkLogs(TestFunctionCall& _call) // Finally we update the consumed logs within the producer. for (auto& logIdx: m_touchedLogs[&producer->call()]) - producer->consumedLogs().insert(logIdx); + producer->consumedLogIndexes().insert(logIdx); // It is ok if some builtins consumed log events. - return producer->consumedLogs().size() >= producer->logs().size(); + return producer->consumedLogIndexes().size() >= producer->logs().size(); } return true; } diff --git a/test/libsolidity/semanticTests/events/event_emit.sol b/test/libsolidity/semanticTests/events/event_emit.sol index e71254151515..e64ecd509481 100644 --- a/test/libsolidity/semanticTests/events/event_emit.sol +++ b/test/libsolidity/semanticTests/events/event_emit.sol @@ -2,17 +2,23 @@ contract ClientReceipt { event A(address _from, bytes32 _id, uint _value); event B(address _from, bytes32 _id, uint indexed _value) anonymous; event C(address _from, bytes32 indexed _id, uint _value); - function deposit(bytes32 _id) public payable { + function deposit(bytes32 _id) public payable returns(uint256) { emit A(msg.sender, _id, msg.value); emit B(msg.sender, _id, msg.value); emit C(msg.sender, _id, msg.value); + + return msg.value; } } // ==== // compileViaYul: false // ---- -// deposit(bytes32), 18 wei: 0x1234 -> -// logs.expectEvent(uint256,string): 0, "A(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 0x12 -// logs.expectEvent(uint256,string): 1, "" -> 0x12, 0x1212121212121212121212121212120000000012, 0x1234 -// logs.expectEvent(uint256,string): 2, "C(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x12 +// deposit(bytes32), 28 wei: 0x1234 -> 0x1c +// logs.expectEvent(uint256,string): 0, "A(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 0x1c +// logs.expectEvent(uint256,string): 1, "" -> 0x1c, 0x1212121212121212121212121212120000000012, 0x1234 +// logs.expectEvent(uint256,string): 2, "C(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x1c +// deposit(bytes32), 23 wei: 0x1234 -> 0x17 +// logs.expectEvent(uint256,string): 0, "A(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 0x17 +// logs.expectEvent(uint256,string): 1, "" -> 0x17, 0x1212121212121212121212121212120000000012, 0x1234 +// logs.expectEvent(uint256,string): 2, "C(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x17 diff --git a/test/libsolidity/util/TestFunctionCall.cpp b/test/libsolidity/util/TestFunctionCall.cpp index 1911e4e420e9..22c7cd8c1667 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -74,6 +74,53 @@ TestFunctionCall::findEventSignature(solidity::test::ExecutionFramework::log_rec return {}; } +string TestFunctionCall::generateExpectation(string _linePrefix, solidity::test::ExecutionFramework::log_record const& _log) const +{ + stringstream sstream; + EventInformation eventInfo = findEventSignature(_log); + string signature{eventInfo.signature}; + sstream << std::endl + << _linePrefix << "// logs.expectEvent(uint256,string): " << _log.index << ", " + << "\"" + signature + "\" -> "; + + vector topicsAndData; + for (auto& topic: _log.topics) + if ((topic != *_log.topics.begin() && !signature.empty()) || signature.empty()) + topicsAndData.emplace_back(u256{topic}); + + assert(_log.data.size() % 32 == 0); + + if (eventInfo.signature.empty()) + { + for (long current = 0; current < static_cast(_log.data.size() / 32); ++current) + { + bytes parameter{_log.data.begin() + current * 32, _log.data.begin() + current * 32 + 32}; + topicsAndData.emplace_back(util::fromBigEndian(parameter)); + } + } + else + { + long current = 0; + for (auto& type: eventInfo.nonIndexedTypes) + { + // todo: use type information to improve decoding. + (void) type; + bytes parameter{_log.data.begin() + current, _log.data.begin() + current + 32}; + current += 32; + topicsAndData.emplace_back(util::fromBigEndian(parameter)); + } + } + + for (auto& element: topicsAndData) + { + sstream << util::toCompactHexWithPrefix(element); + if (&element != &*topicsAndData.rbegin()) + sstream << ", "; + } + + return sstream.str(); +} + string TestFunctionCall::format( ErrorReporter& _errorReporter, string const& _linePrefix, @@ -88,14 +135,8 @@ string TestFunctionCall::format( auto formatOutput = [&](bool const _singleLine) { string ws = " "; - string arrow = formatToken(Token::Arrow); - string colon = formatToken(Token::Colon); - string comma = formatToken(Token::Comma); - string comment = formatToken(Token::Comment); - string ether = formatToken(Token::Ether); - string wei = formatToken(Token::Wei); string newline = formatToken(Token::Newline); - string failure = formatToken(Token::Failure); + string colon = formatToken(Token::Colon); if (m_call.kind == FunctionCall::Kind::Library) { @@ -116,208 +157,185 @@ string TestFunctionCall::format( return; } - - /// Formats the function signature. This is the same independent from the display-mode. - stream << _linePrefix << newline << ws << m_call.signature; - if (m_call.value.value > u256(0)) + else if (m_call.kind == FunctionCall::Kind::Builtin) { - if (m_call.value.unit == FunctionValueUnit::Ether) - stream << comma << ws << (m_call.value.value / exp256(10, 18)) << ws << ether; - else if (m_call.value.unit == FunctionValueUnit::Wei) - stream << comma << ws << m_call.value.value << ws << wei; - else - soltestAssert(false, ""); + // + return; } - if (!m_call.arguments.rawBytes().empty()) + else if (hasUnconsumedLogs()) { - string output = formatRawParameters(m_call.arguments.parameters, _linePrefix); - stream << colon; - if (!m_call.arguments.parameters.at(0).format.newline) - stream << ws; - stream << output; - } + formatFunctionCall(_linePrefix, _renderResult, _singleLine, !matchesExpectation(), _errorReporter, stream); - /// Formats comments on the function parameters and the arrow taking - /// the display-mode into account. - if (_singleLine) - { - if (!m_call.arguments.comment.empty()) - stream << ws << comment << m_call.arguments.comment << comment; + for (auto& log: consumedLogs()) + stream << generateExpectation(_linePrefix, log); + + std::stringstream sstream; + for (auto& log: unconsumedLogs()) + sstream << generateExpectation(_linePrefix, log); - if (m_call.omitsArrow) + if (highlight) { - if (_renderResult && (m_failure || !matchesExpectation())) - stream << ws << arrow; - } - else + AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << sstream.str(); + AnsiColorized(stream, highlight, {util::formatting::RESET}); + } else + stream << sstream.str(); + return; + } + else + formatFunctionCall(_linePrefix, _renderResult, _singleLine, highlight, _errorReporter, stream); + }; + + formatOutput(m_call.displayMode == FunctionCall::DisplayMode::SingleLine); + return stream.str(); +} + +void TestFunctionCall::formatFunctionCall( + const string& _linePrefix, + const bool _renderResult, + const bool _singleLine, + const bool _highlight, + ErrorReporter& _errorReporter, + stringstream& stream) const +{ + string ws = " "; + string arrow = formatToken(Token::Arrow); + string colon = formatToken(Token::Colon); + string comma = formatToken(Token::Comma); + string comment = formatToken(Token::Comment); + string ether = formatToken(Token::Ether); + string wei = formatToken(Token::Wei); + string newline = formatToken(Token::Newline); + string failure = formatToken(Token::Failure); + + /// Formats the function signature. This is the same independent from the display-mode. + stream << _linePrefix << newline << ws << m_call.signature; + if (m_call.value.value > u256(0)) + { + if (m_call.value.unit == FunctionValueUnit::Ether) + stream << comma << ws << (m_call.value.value / exp256(10, 18)) << ws << ether; + else if (m_call.value.unit == FunctionValueUnit::Wei) + stream << comma << ws << m_call.value.value << ws << wei; + else + soltestAssert(false, ""); + } + if (!m_call.arguments.rawBytes().empty()) + { + string output = formatRawParameters(m_call.arguments.parameters, _linePrefix); + stream << colon; + if (!m_call.arguments.parameters.at(0).format.newline) + stream << ws; + stream << output; + } + + /// Formats comments on the function parameters and the arrow taking + /// the display-mode into account. + if (_singleLine) + { + if (!m_call.arguments.comment.empty()) + stream << ws << comment << m_call.arguments.comment << comment; + + if (m_call.omitsArrow) + { + if (_renderResult && (m_failure || !matchesExpectation())) stream << ws << arrow; } else + stream << ws << arrow; + } + else + { + stream << endl << _linePrefix << newline << ws; + if (!m_call.arguments.comment.empty()) { + stream << comment << m_call.arguments.comment << comment; stream << endl << _linePrefix << newline << ws; - if (!m_call.arguments.comment.empty()) - { - stream << comment << m_call.arguments.comment << comment; - stream << endl << _linePrefix << newline << ws; - } - stream << arrow; } + stream << arrow; + } - /// Format either the expected output or the actual result output - string result; - auto& builtin = m_call.expectations.builtin; - if (hasUnconsumedLogs()) + /// Format either the expected output or the actual result output + string result; + auto& builtin = m_call.expectations.builtin; + if (!_renderResult) + { + bool const isFailure = m_call.expectations.failure; + result = isFailure ? formatFailure(_errorReporter, m_call, m_rawBytes, _renderResult, _highlight) + : formatRawParameters(m_call.expectations.result); + if (!result.empty()) { - for (auto& log: unconsumedLogs()) - { - EventInformation eventInfo = findEventSignature(log); - string signature{eventInfo.signature}; - stream << std::endl - << "// logs.expectEvent(uint256,string): " << log.index << ", " - << "\"" + signature + "\" -> "; - - vector topicsAndData; - for (auto& topic: log.topics) - if ((topic != *log.topics.begin() && !signature.empty()) || signature.empty()) - topicsAndData.emplace_back(u256{topic}); - - assert(log.data.size() % 32 == 0); - - if (eventInfo.signature.empty()) - { - for (long current = 0; current < static_cast(log.data.size() / 32); ++current) - { - bytes parameter{log.data.begin() + current * 32, log.data.begin() + current * 32 + 32}; - topicsAndData.emplace_back(util::fromBigEndian(parameter)); - } - } - else - { - long current = 0; - for (auto& type: eventInfo.nonIndexedTypes) - { - // todo: use type information to improve decoding. - (void) type; - bytes parameter{log.data.begin() + current, log.data.begin() + current + 32}; - current += 32; - topicsAndData.emplace_back(util::fromBigEndian(parameter)); - } - } - for (auto& element: topicsAndData) - { - stream << util::toCompactHexWithPrefix(element); - if (&element != &*topicsAndData.rbegin()) - stream << ", "; - } - } + AnsiColorized(stream, _highlight, {util::formatting::RESET}) << ws; + AnsiColorized(stream, _highlight, {util::formatting::RED_BACKGROUND}) << result; } - if (!_renderResult) + } + else + { + if (m_calledNonExistingFunction) + _errorReporter.warning("The function \"" + m_call.signature + "\" is not known to the compiler."); + + if (m_consumedLogIndexes.size() != m_rawLogs.size()) { - if (builtin) - { - result = builtin->signature; - if (!builtin->arguments.parameters.empty()) - result += ": "; - result += formatRawParameters(builtin->arguments.parameters); - } - else - { - bool const isFailure = m_call.expectations.failure; - result = isFailure ? - formatFailure(_errorReporter, m_call, m_rawBytes, _renderResult, highlight) : - formatRawParameters(m_call.expectations.result); - } - if (!result.empty()) - { - AnsiColorized(stream, false, {util::formatting::RESET}) << ws; - AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << result; - } + stringstream str; + str << "The function \"" << m_call.signature << "\" produced " << m_rawLogs.size() << " log(s), but only " + << m_consumedLogIndexes.size() << " log(s) where consumed."; + _errorReporter.error(str.str()); } - else - { - if (m_calledNonExistingFunction) - _errorReporter.warning("The function \"" + m_call.signature + "\" is not known to the compiler."); - if (m_consumedLogs.size() != m_rawLogs.size()) - { - stringstream str; - str << "The function \"" << m_call.signature << "\" produced " << m_rawLogs.size() <<" log(s), but only " << m_consumedLogs.size() << " log(s) where consumed."; - _errorReporter.error(str.str()); - } + if (builtin) + _errorReporter.warning( + "The expectation \"" + builtin->signature + ": " + formatRawParameters(builtin->arguments.parameters) + + "\" will be replaced with the actual value returned by the test."); - if (builtin) - _errorReporter.warning("The expectation \"" + builtin->signature + ": " + formatRawParameters(builtin->arguments.parameters) + "\" will be replaced with the actual value returned by the test."); - - bytes output = m_rawBytes; - bool const isFailure = m_failure; - result = isFailure ? - formatFailure(_errorReporter, m_call, output, _renderResult, highlight) : - matchesExpectation() ? - formatRawParameters(m_call.expectations.result) : - formatBytesParameters( - _errorReporter, - output, - m_call.signature, - m_call.expectations.result, - highlight - ); - - if (!matchesExpectation()) - { - std::optional abiParams; + bytes output = m_rawBytes; + bool const isFailure = m_failure; + result = isFailure ? formatFailure(_errorReporter, m_call, output, _renderResult, _highlight) + : matchesExpectation() + ? formatRawParameters(m_call.expectations.result) + : formatBytesParameters( + _errorReporter, output, m_call.signature, m_call.expectations.result, _highlight); - if (isFailure) - { - if (!output.empty()) - abiParams = ContractABIUtils::failureParameters(output); - } - else - abiParams = ContractABIUtils::parametersFromJsonOutputs( - _errorReporter, - m_contractABI, - m_call.signature - ); - - string bytesOutput = abiParams ? - BytesUtils::formatRawBytes(output, abiParams.value(), _linePrefix) : - BytesUtils::formatRawBytes( - output, - ContractABIUtils::defaultParameters((output.size() + 31) / 32), - _linePrefix - ); - - _errorReporter.warning( - "The call to \"" + m_call.signature + "\" returned \n" + - bytesOutput - ); - } + if (!matchesExpectation()) + { + std::optional abiParams; if (isFailure) - AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << ws << result; + { + if (!output.empty()) + abiParams = ContractABIUtils::failureParameters(output); + } else - if (!result.empty()) - stream << ws << result; + abiParams + = ContractABIUtils::parametersFromJsonOutputs(_errorReporter, m_contractABI, m_call.signature); - } + string bytesOutput + = abiParams ? BytesUtils::formatRawBytes(output, abiParams.value(), _linePrefix) + : BytesUtils::formatRawBytes( + output, ContractABIUtils::defaultParameters((output.size() + 31) / 32), _linePrefix); - /// Format comments on expectations taking the display-mode into account. - if (_singleLine) - { - if (!m_call.expectations.comment.empty()) - stream << ws << comment << m_call.expectations.comment << comment; + _errorReporter.warning("The call to \"" + m_call.signature + "\" returned \n" + bytesOutput); } - else + + if (isFailure) + AnsiColorized(stream, _highlight, {util::formatting::RED_BACKGROUND}) << ws << result; + else if (!result.empty()) + stream << ws << result; + } + + /// Format comments on expectations taking the display-mode into account. + if (_singleLine) + { + if (!m_call.expectations.comment.empty()) + stream << ws << comment << m_call.expectations.comment << comment; + } + else + { + if (!m_call.expectations.comment.empty()) { - if (!m_call.expectations.comment.empty()) - { - stream << endl << _linePrefix << newline << ws; - stream << comment << m_call.expectations.comment << comment; - } + stream << endl << _linePrefix << newline << ws; + stream << comment << m_call.expectations.comment << comment; } - }; - - formatOutput(m_call.displayMode == FunctionCall::DisplayMode::SingleLine); - return stream.str(); + } + for (auto& log: consumedLogs()) + stream << generateExpectation(_linePrefix, log); } string TestFunctionCall::formatBytesParameters( @@ -326,8 +344,7 @@ string TestFunctionCall::formatBytesParameters( string const& _signature, solidity::frontend::test::ParameterList const& _parameters, bool _highlight, - bool _failure -) const + bool _failure) const { using ParameterList = solidity::frontend::test::ParameterList; diff --git a/test/libsolidity/util/TestFunctionCall.h b/test/libsolidity/util/TestFunctionCall.h index 56f0b97b61e4..77c9b7effe7c 100644 --- a/test/libsolidity/util/TestFunctionCall.h +++ b/test/libsolidity/util/TestFunctionCall.h @@ -90,22 +90,32 @@ class TestFunctionCall { m_rawLogs = std::move(_logs); } - std::set& consumedLogs() + std::set& consumedLogIndexes() { - return m_consumedLogs; + return m_consumedLogIndexes; } bool hasUnconsumedLogs() const { - return m_consumedLogs.size() < m_rawLogs.size(); + return m_consumedLogIndexes.size() < m_rawLogs.size(); } - std::vector unconsumedLogs() const + std::vector logs(bool _consumed) const { std::vector result; for (auto& log: m_rawLogs) - if (m_consumedLogs.find(log.index) == m_consumedLogs.end()) + if (_consumed && m_consumedLogIndexes.find(log.index) != m_consumedLogIndexes.end()) + result.emplace_back(m_rawLogs[log.index]); + else if (!_consumed && m_consumedLogIndexes.find(log.index) == m_consumedLogIndexes.end()) result.emplace_back(m_rawLogs[log.index]); return result; } + std::vector consumedLogs() const + { + return logs(true); + } + std::vector unconsumedLogs() const + { + return logs(false); + } void setPreviousCall(TestFunctionCall* _call) { m_previousCall = _call; @@ -168,7 +178,7 @@ class TestFunctionCall bytes m_rawBytes = bytes{}; /// Logs created by the actual call. std::vector m_rawLogs{}; - std::set m_consumedLogs; + std::set m_consumedLogIndexes; /// Transaction status of the actual call. False in case of a REVERT or any other failure. bool m_failure = true; @@ -179,6 +189,15 @@ class TestFunctionCall bool m_calledNonExistingFunction = false; TestFunctionCall* m_previousCall = nullptr; + void formatFunctionCall( + const std::string& _linePrefix, + const bool _renderResult, + const bool _singleLine, + const bool _highlight, + ErrorReporter& _errorReporter, + std::stringstream& _stream) const; + + std::string generateExpectation(std::string _linePrefix, solidity::test::ExecutionFramework::log_record const& _log) const; }; }