Skip to content

Commit 6418b58

Browse files
committed
Add logs.expectEvent(..). Initial log tracking.
1 parent 2839aff commit 6418b58

File tree

5 files changed

+177
-10
lines changed

5 files changed

+177
-10
lines changed

test/ExecutionFramework.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ class ExecutionFramework
5151
{
5252

5353
public:
54+
/// LOG record.
55+
struct log_record
56+
{
57+
/// The address of the account which created the log.
58+
util::h160 creator;
59+
60+
/// The data attached to the log.
61+
bytes data;
62+
63+
/// The log topics.
64+
std::vector<util::h256> topics;
65+
66+
/// Equal operator.
67+
bool operator==(const log_record& other) const noexcept
68+
{
69+
return creator == other.creator && data == other.data && topics == other.topics;
70+
}
71+
};
72+
5473
ExecutionFramework();
5574
ExecutionFramework(langutil::EVMVersion _evmVersion, std::vector<boost::filesystem::path> const& _vmPaths);
5675
virtual ~ExecutionFramework() = default;
@@ -277,6 +296,22 @@ class ExecutionFramework
277296
util::h160 logAddress(size_t _logIdx) const;
278297
bytes logData(size_t _logIdx) const;
279298

299+
std::vector<log_record> recordedLogs() {
300+
std::vector<log_record> logs{};
301+
for (size_t logIdx = 0; logIdx < numLogs(); ++logIdx)
302+
{
303+
log_record record;
304+
const auto& data = m_evmcHost->recorded_logs.at(logIdx).data;
305+
record.data = bytes{data.begin(), data.end()};
306+
record.creator = EVMHost::convertFromEVMC(m_evmcHost->recorded_logs.at(logIdx).creator);
307+
for (size_t topicIdx = 0; topicIdx < numLogTopics(logIdx); ++topicIdx) {
308+
record.topics.emplace_back(EVMHost::convertFromEVMC(m_evmcHost->recorded_logs.at(logIdx).topics.at(topicIdx)));
309+
}
310+
logs.emplace_back(record);
311+
}
312+
return logs;
313+
}
314+
280315
langutil::EVMVersion m_evmVersion;
281316
solidity::frontend::RevertStrings m_revertStrings = solidity::frontend::RevertStrings::Default;
282317
solidity::frontend::OptimiserSettings m_optimiserSettings = solidity::frontend::OptimiserSettings::minimal();

test/libsolidity/SemanticTest.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer
5656
{"logTopic(uint256,uint256)", std::bind(&SemanticTest::logTopic, this, _1)},
5757
{"logAddress(uint256)", std::bind(&SemanticTest::logAddress, this, _1)},
5858
{"logData(uint256)", std::bind(&SemanticTest::logData, this, _1)},
59+
{"expectEvent(uint256,string)", std::bind(&SemanticTest::expectEvent, this, _1)},
5960
}}};
6061

6162
string choice = m_reader.stringSetting("compileViaYul", "default");
@@ -142,6 +143,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
142143
selectVM(evmc_capabilities::EVMC_CAPABILITY_EVM1);
143144

144145
reset();
146+
m_touchedLogs.clear();
145147

146148
m_compileViaYul = _compileViaYul;
147149
if (_compileToEwasm)
@@ -162,6 +164,14 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
162164

163165
bool constructed = false;
164166

167+
// Iterate through the test calls and set the previous call.
168+
TestFunctionCall* previousCall{nullptr};
169+
for (auto& test: m_tests)
170+
{
171+
test.setPreviousCall(previousCall);
172+
previousCall = &test;
173+
}
174+
165175
for (auto& test: m_tests)
166176
{
167177
if (constructed)
@@ -261,6 +271,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
261271

262272
if (m_transactionSuccessful != !test.call().expectations.failure || outputMismatch)
263273
success = false;
274+
success = checkLogs(test);
264275
test.setFailure(!m_transactionSuccessful);
265276
}
266277

@@ -269,6 +280,10 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
269280
}
270281
}
271282

283+
TestFunctionCall fakeLastTestFunctionCall(FunctionCall{});
284+
fakeLastTestFunctionCall.setPreviousCall(previousCall);
285+
success = checkLogs(fakeLastTestFunctionCall);
286+
272287
if (success && !m_runWithYul && _compileViaYul)
273288
{
274289
m_compileViaYulCanBeSet = true;
@@ -404,6 +419,43 @@ void SemanticTest::printUpdatedSettings(ostream& _stream, string const& _linePre
404419
_stream << _linePrefix << "// " << setting.first << ": " << setting.second << endl;
405420
}
406421

422+
bool SemanticTest::checkLogs(TestFunctionCall& _call)
423+
{
424+
_call.setLogs(SolidityExecutionFramework::recordedLogs());
425+
426+
TestFunctionCall* producer = _call.previousCall();
427+
std::vector<FunctionCall const*> consumers{};
428+
// Only non-builtins are able to produce logs.
429+
// So lets search from the current call up to the first non-builtin.
430+
while (producer != nullptr && producer->call().kind == FunctionCall::Kind::Builtin)
431+
if (producer->previousCall() != nullptr)
432+
{
433+
// On the way up to the producer we track all builtins that where on the way.
434+
// Only builtins can consume logs, we store them in the consumers vector.
435+
consumers.emplace_back(&producer->call());
436+
producer = producer->previousCall();
437+
}
438+
439+
// Producer will now point to the call that probably produced a log.
440+
if (producer)
441+
{
442+
// We iterate through the consumers to find out what logs they have consumed.
443+
for (auto& consumer: consumers)
444+
for (auto logIdx: m_touchedLogs[consumer])
445+
// All logs that where touched by the consumer, will be marked as
446+
// touched within the producer.
447+
touchLog(producer->call(), logIdx);
448+
449+
// Finally we update the consumed logs within the producer.
450+
for (auto& logIdx: m_touchedLogs[&producer->call()])
451+
producer->consumedLogs().insert(logIdx);
452+
453+
std::cout << producer->consumedLogs().size() << " / " << producer->logs().size() << std::endl;
454+
return producer->consumedLogs().size() == producer->logs().size();
455+
}
456+
return true;
457+
}
458+
407459
bytes SemanticTest::numLogs(FunctionCall const&)
408460
{
409461
// numLogs()
@@ -419,6 +471,7 @@ bytes SemanticTest::numLogTopics(FunctionCall const& call)
419471
size_t logCount = SolidityExecutionFramework::numLogs();
420472
// todo: hex strings not supported by lexical_cast<..>(..)
421473
auto logIdx = lexical_cast<size_t>(call.arguments.parameters.front().rawString);
474+
touchLog(call, logIdx);
422475
if (logCount > 0 && logIdx < logCount)
423476
return util::toBigEndian(u256{SolidityExecutionFramework::numLogTopics(logIdx)});
424477
// empty result means failure.
@@ -430,6 +483,7 @@ bytes SemanticTest::logTopic(FunctionCall const& call)
430483
// logTopic(uint256,uint256)
431484
assert(call.arguments.parameters.size() == 2);
432485
auto logIdx = lexical_cast<size_t>(call.arguments.parameters.front().rawString);
486+
touchLog(call, logIdx);
433487
auto topicIdx = lexical_cast<size_t>(call.arguments.parameters.back().rawString);
434488
size_t logCount = SolidityExecutionFramework::numLogs();
435489
// todo: hex strings not supported by lexical_cast<..>(..)
@@ -449,6 +503,7 @@ bytes SemanticTest::logAddress(FunctionCall const& call)
449503
size_t logCount = SolidityExecutionFramework::numLogs();
450504
// todo: hex strings not supported by lexical_cast<..>(..)
451505
auto logIdx = lexical_cast<size_t>(call.arguments.parameters.front().rawString);
506+
touchLog(call, logIdx);
452507
if (logCount > 0 && logIdx < logCount)
453508
return util::toBigEndian(u256{u160{SolidityExecutionFramework::logAddress(logIdx)}});
454509
// empty result means failure.
@@ -462,12 +517,45 @@ bytes SemanticTest::logData(FunctionCall const& call)
462517
size_t logCount = SolidityExecutionFramework::numLogs();
463518
// todo: hex strings not supported by lexical_cast<..>(..)
464519
auto logIdx = lexical_cast<size_t>(call.arguments.parameters.front().rawString);
520+
touchLog(call, logIdx);
465521
if (logCount > 0 && logIdx < logCount)
466522
return SolidityExecutionFramework::logData(logIdx);
467523
// empty result means failure.
468524
return bytes{};
469525
}
470526

527+
bytes SemanticTest::expectEvent(FunctionCall const& call)
528+
{
529+
// expectEvent(uint256,string): logIdx, eventSignature
530+
assert(call.arguments.parameters.size() == 2);
531+
size_t logCount = SolidityExecutionFramework::numLogs();
532+
// todo: hex strings not supported by lexical_cast<..>(..)
533+
auto logIdx = lexical_cast<size_t>(call.arguments.parameters.front().rawString);
534+
touchLog(call, logIdx);
535+
auto logSignature = call.arguments.parameters.back().rawString;
536+
assert(logSignature.length() >= 2);
537+
logSignature = logSignature.substr(1, logSignature.length() - 2);
538+
h256 logSignatureHash{util::keccak256(logSignature)};
539+
if (logCount > 0 && logIdx < logCount)
540+
{
541+
vector<h256> topics;
542+
size_t topicCount = SolidityExecutionFramework::numLogTopics(logIdx);
543+
for (size_t topicIdx = 0; topicIdx < topicCount; ++topicIdx)
544+
topics.push_back(SolidityExecutionFramework::logTopic(logIdx, topicIdx));
545+
// remove topics[0], if the signature matches.
546+
if (!topics.empty() && topics[0] == logSignatureHash)
547+
topics.erase(topics.begin());
548+
bytes result;
549+
for (auto& topic : topics)
550+
result += util::toBigEndian(topic);
551+
result += SolidityExecutionFramework::logData(logIdx);
552+
// todo: anonymous events with no data would be treated as error, maybe not that important.
553+
return result;
554+
}
555+
// empty result means failure.
556+
return bytes{};
557+
}
558+
471559
void SemanticTest::parseExpectations(istream& _stream)
472560
{
473561
TestFileParser parser{_stream, &this->m_builtins};

test/libsolidity/SemanticTest.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,19 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
6060
bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map<std::string, solidity::test::Address> const& _libraries = {});
6161

6262
private:
63+
bool checkLogs(TestFunctionCall& _call);
64+
6365
// logs builtins.
6466
bytes numLogs(FunctionCall const& call);
6567
bytes numLogTopics(FunctionCall const& call);
6668
bytes logTopic(FunctionCall const& call);
6769
bytes logAddress(FunctionCall const& call);
6870
bytes logData(FunctionCall const& call);
71+
bytes expectEvent(FunctionCall const& call);
72+
void touchLog(FunctionCall const& call, size_t _logIdx)
73+
{
74+
m_touchedLogs[&call].insert(_logIdx);
75+
}
6976

7077
TestResult runTest(std::ostream& _stream, std::string const& _linePrefix, bool _formatted, bool _compileViaYul, bool _compileToEwasm);
7178
SourceMap m_sources;
@@ -79,6 +86,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
7986
bool m_allowNonExistingFunctions = false;
8087
bool m_compileViaYulCanBeSet = false;
8188
BuiltinFunctions m_builtins;
89+
std::map<FunctionCall const*, std::set<size_t>> m_touchedLogs;
8290
};
8391

8492
}
Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
contract ClientReceipt {
2-
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
2+
event D(address indexed _from, bytes32 indexed _id, uint _value);
3+
event D2(address indexed _from, bytes32 indexed _id, uint _value) anonymous;
4+
event D3(address _from, bytes32 indexed _id, uint _value);
35
function deposit(bytes32 _id) public payable {
4-
emit Deposit(msg.sender, _id, msg.value);
6+
emit D(msg.sender, _id, msg.value);
7+
emit D2(msg.sender, _id, msg.value);
8+
emit D3(msg.sender, _id, msg.value);
9+
}
10+
function deposit2(bytes32 _id) public payable {
11+
emit D(msg.sender, _id, msg.value);
512
}
613
}
14+
15+
// logs.expectEvent(uint256,string): 0, "D(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 18
16+
17+
// deposit2(bytes32), 18 wei: 0x1234 ->
18+
// deposit(bytes32), 18 wei: 0x1234 ->
719
// ====
8-
// compileViaYul: also
20+
// compileViaYul: false
921
// ----
1022
// deposit(bytes32), 18 wei: 0x1234 ->
11-
// logs.numLogs() -> 1
12-
// logs.logAddress(uint256): 0 -> 0x0fdd67305928fcac8d213d1e47bfa6165cd0b87b
13-
// logs.logData(uint256): 0 -> 0x12
14-
// logs.numLogTopics(uint256): 0 -> 3
15-
// logs.logTopic(uint256,uint256): 0, 0 -> 0x19dacbf83c5de6658e14cbf7bcae5c15eca2eedecf1c66fbca928e4d351bea0f
16-
// logs.logTopic(uint256,uint256): 0, 1 -> 0x1212121212121212121212121212120000000012
17-
// logs.logTopic(uint256,uint256): 0, 2 -> 0x1234
23+
// logs.expectEvent(uint256,string): 0, "D(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 18
24+
// logs.expectEvent(uint256,string): 1, "D2(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 18
25+
// logs.expectEvent(uint256,string): 2, "D3(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x12

test/libsolidity/util/TestFunctionCall.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <test/libsolidity/util/TestFileParser.h>
1818
#include <test/libsolidity/util/SoltestErrors.h>
19+
#include <test/ExecutionFramework.h>
1920

2021
#include <liblangutil/Exceptions.h>
2122
#include <libsolutil/AnsiColorized.h>
@@ -27,6 +28,7 @@
2728
#include <numeric>
2829
#include <stdexcept>
2930
#include <string>
31+
#include <utility>
3032
#include <vector>
3133

3234
namespace solidity::frontend::test
@@ -80,6 +82,26 @@ class TestFunctionCall
8082
void setFailure(const bool _failure) { m_failure = _failure; }
8183
void setRawBytes(const bytes _rawBytes) { m_rawBytes = _rawBytes; }
8284
void setContractABI(Json::Value _contractABI) { m_contractABI = std::move(_contractABI); }
85+
std::vector<solidity::test::ExecutionFramework::log_record> logs()
86+
{
87+
return m_rawLogs;
88+
}
89+
void setLogs(std::vector<solidity::test::ExecutionFramework::log_record> _logs)
90+
{
91+
m_rawLogs = std::move(_logs);
92+
}
93+
std::set<size_t>& consumedLogs()
94+
{
95+
return m_consumedLogs;
96+
}
97+
void setPreviousCall(TestFunctionCall* _call)
98+
{
99+
m_previousCall = _call;
100+
}
101+
TestFunctionCall* previousCall()
102+
{
103+
return m_previousCall;
104+
}
83105

84106
private:
85107
/// Tries to format the given `bytes`, applying the detected ABI types that have be set for each parameter.
@@ -124,13 +146,19 @@ class TestFunctionCall
124146
FunctionCall m_call;
125147
/// Result of the actual call been made.
126148
bytes m_rawBytes = bytes{};
149+
/// Logs created by the actual call.
150+
std::vector<solidity::test::ExecutionFramework::log_record> m_rawLogs{};
151+
std::set<size_t> m_consumedLogs;
152+
127153
/// Transaction status of the actual call. False in case of a REVERT or any other failure.
128154
bool m_failure = true;
129155
/// JSON object which holds the contract ABI and that is used to set the output formatting
130156
/// in the interactive update routine.
131157
Json::Value m_contractABI;
132158
/// Flags that the test failed because the called function is not known to exist on the contract.
133159
bool m_calledNonExistingFunction = false;
160+
161+
TestFunctionCall* m_previousCall = nullptr;
134162
};
135163

136164
}

0 commit comments

Comments
 (0)