Skip to content

Commit d6375bd

Browse files
committed
Basic expectation generation, if events where not consumed.
1 parent 3847073 commit d6375bd

File tree

5 files changed

+135
-26
lines changed

5 files changed

+135
-26
lines changed

test/ExecutionFramework.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ class ExecutionFramework
5151
{
5252

5353
public:
54-
/// LOG record.
5554
struct log_record
5655
{
56+
size_t index;
5757
/// The address of the account which created the log.
5858
util::h160 creator;
5959

@@ -302,6 +302,7 @@ class ExecutionFramework
302302
{
303303
log_record record;
304304
const auto& data = m_evmcHost->recorded_logs.at(logIdx).data;
305+
record.index = logIdx;
305306
record.data = bytes{data.begin(), data.end()};
306307
record.creator = EVMHost::convertFromEVMC(m_evmcHost->recorded_logs.at(logIdx).creator);
307308
for (size_t topicIdx = 0; topicIdx < numLogTopics(logIdx); ++topicIdx) {

test/libsolidity/SemanticTest.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
282282

283283
TestFunctionCall fakeLastTestFunctionCall(FunctionCall{});
284284
fakeLastTestFunctionCall.setPreviousCall(previousCall);
285-
success = checkLogs(fakeLastTestFunctionCall);
285+
success &= checkLogs(fakeLastTestFunctionCall);
286286

287287
if (success && !m_runWithYul && _compileViaYul)
288288
{
@@ -428,7 +428,9 @@ bool SemanticTest::checkLogs(TestFunctionCall& _call)
428428
// Only non-builtins are able to produce logs.
429429
// So lets search from the current call up to the first non-builtin.
430430
while (producer != nullptr && producer->call().kind == FunctionCall::Kind::Builtin)
431-
if (producer->previousCall() != nullptr)
431+
if (producer->previousCall() == nullptr)
432+
break;
433+
else
432434
{
433435
// On the way up to the producer we track all builtins that where on the way.
434436
// Only builtins can consume logs, we store them in the consumers vector.
@@ -450,8 +452,8 @@ bool SemanticTest::checkLogs(TestFunctionCall& _call)
450452
for (auto& logIdx: m_touchedLogs[&producer->call()])
451453
producer->consumedLogs().insert(logIdx);
452454

453-
std::cout << producer->consumedLogs().size() << " / " << producer->logs().size() << std::endl;
454-
return producer->consumedLogs().size() == producer->logs().size();
455+
// It is ok if some builtins consumed log events.
456+
return producer->consumedLogs().size() >= producer->logs().size();
455457
}
456458
return true;
457459
}
Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
contract ClientReceipt {
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);
2+
event A(address _from, bytes32 _id, uint _value);
3+
event B(address _from, bytes32 _id, uint indexed _value) anonymous;
4+
event C(address _from, bytes32 indexed _id, uint _value);
55
function deposit(bytes32 _id) public payable {
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);
6+
emit A(msg.sender, _id, msg.value);
7+
emit B(msg.sender, _id, msg.value);
8+
emit C(msg.sender, _id, msg.value);
129
}
1310
}
1411

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 ->
1912
// ====
2013
// compileViaYul: false
2114
// ----
2215
// deposit(bytes32), 18 wei: 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
16+
// logs.expectEvent(uint256,string): 0, "A(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 0x12
17+
// logs.expectEvent(uint256,string): 1, "" -> 0x12, 0x1212121212121212121212121212120000000012, 0x1234
18+
// logs.expectEvent(uint256,string): 2, "C(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x12

test/libsolidity/util/TestFunctionCall.cpp

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
#include <test/libsolidity/util/ContractABIUtils.h>
1919

2020
#include <libsolutil/AnsiColorized.h>
21+
#include <libsolutil/JSON.h>
2122

23+
#include <boost/algorithm/string/predicate.hpp>
2224
#include <boost/algorithm/string/replace.hpp>
2325

2426
#include <optional>
@@ -32,6 +34,46 @@ using namespace std;
3234

3335
using Token = soltest::Token;
3436

37+
TestFunctionCall::EventInformation
38+
TestFunctionCall::findEventSignature(solidity::test::ExecutionFramework::log_record const& log) const
39+
{
40+
for (const auto& entity: m_contractABI)
41+
{
42+
EventInformation eventInfo;
43+
if (entity["type"].isString() && entity["type"].asString() == "event")
44+
{
45+
string name{entity["name"].asString()};
46+
bool anonymous{entity["anonymous"].asBool()};
47+
vector<string> argumentTypes;
48+
for (const auto& input: entity["inputs"])
49+
{
50+
string type{input["internalType"].asString()};
51+
argumentTypes.emplace_back(type);
52+
if (input["indexed"].asBool())
53+
eventInfo.indexedTypes.push_back(type);
54+
else
55+
eventInfo.nonIndexedTypes.push_back(type);
56+
}
57+
if (!name.empty() && !anonymous)
58+
{
59+
std::string signature{name};
60+
signature += "(";
61+
for (auto& arg: argumentTypes)
62+
signature += arg + ",";
63+
if (!argumentTypes.empty())
64+
signature = signature.substr(0, signature.length() - 1);
65+
signature += ")";
66+
if (!log.topics.empty() && log.topics[0] == util::keccak256(signature))
67+
{
68+
eventInfo.signature = signature;
69+
return eventInfo;
70+
}
71+
}
72+
}
73+
}
74+
return {};
75+
}
76+
3577
string TestFunctionCall::format(
3678
ErrorReporter& _errorReporter,
3779
string const& _linePrefix,
@@ -41,7 +83,7 @@ string TestFunctionCall::format(
4183
{
4284
stringstream stream;
4385

44-
bool highlight = !matchesExpectation() && _highlight;
86+
bool highlight = (!matchesExpectation() || hasUnconsumedLogs()) && _highlight;
4587

4688
auto formatOutput = [&](bool const _singleLine)
4789
{
@@ -93,7 +135,6 @@ string TestFunctionCall::format(
93135
if (!m_call.arguments.parameters.at(0).format.newline)
94136
stream << ws;
95137
stream << output;
96-
97138
}
98139

99140
/// Formats comments on the function parameters and the arrow taking
@@ -116,15 +157,60 @@ string TestFunctionCall::format(
116157
stream << endl << _linePrefix << newline << ws;
117158
if (!m_call.arguments.comment.empty())
118159
{
119-
stream << comment << m_call.arguments.comment << comment;
120-
stream << endl << _linePrefix << newline << ws;
160+
stream << comment << m_call.arguments.comment << comment;
161+
stream << endl << _linePrefix << newline << ws;
121162
}
122163
stream << arrow;
123164
}
124165

125166
/// Format either the expected output or the actual result output
126167
string result;
127168
auto& builtin = m_call.expectations.builtin;
169+
if (hasUnconsumedLogs())
170+
{
171+
for (auto& log: unconsumedLogs())
172+
{
173+
EventInformation eventInfo = findEventSignature(log);
174+
string signature{eventInfo.signature};
175+
stream << std::endl
176+
<< "// logs.expectEvent(uint256,string): " << log.index << ", "
177+
<< "\"" + signature + "\" -> ";
178+
179+
vector<u256> topicsAndData;
180+
for (auto& topic: log.topics)
181+
if ((topic != *log.topics.begin() && !signature.empty()) || signature.empty())
182+
topicsAndData.emplace_back(u256{topic});
183+
184+
assert(log.data.size() % 32 == 0);
185+
186+
if (eventInfo.signature.empty())
187+
{
188+
for (long current = 0; current < static_cast<long>(log.data.size() / 32); ++current)
189+
{
190+
bytes parameter{log.data.begin() + current * 32, log.data.begin() + current * 32 + 32};
191+
topicsAndData.emplace_back(util::fromBigEndian<u256>(parameter));
192+
}
193+
}
194+
else
195+
{
196+
long current = 0;
197+
for (auto& type: eventInfo.nonIndexedTypes)
198+
{
199+
// todo: use type information to improve decoding.
200+
(void) type;
201+
bytes parameter{log.data.begin() + current, log.data.begin() + current + 32};
202+
current += 32;
203+
topicsAndData.emplace_back(util::fromBigEndian<u256>(parameter));
204+
}
205+
}
206+
for (auto& element: topicsAndData)
207+
{
208+
stream << util::toCompactHexWithPrefix(element);
209+
if (&element != &*topicsAndData.rbegin())
210+
stream << ", ";
211+
}
212+
}
213+
}
128214
if (!_renderResult)
129215
{
130216
if (builtin)
@@ -152,6 +238,13 @@ string TestFunctionCall::format(
152238
if (m_calledNonExistingFunction)
153239
_errorReporter.warning("The function \"" + m_call.signature + "\" is not known to the compiler.");
154240

241+
if (m_consumedLogs.size() != m_rawLogs.size())
242+
{
243+
stringstream str;
244+
str << "The function \"" << m_call.signature << "\" produced " << m_rawLogs.size() <<" log(s), but only " << m_consumedLogs.size() << " log(s) where consumed.";
245+
_errorReporter.error(str.str());
246+
}
247+
155248
if (builtin)
156249
_errorReporter.warning("The expectation \"" + builtin->signature + ": " + formatRawParameters(builtin->arguments.parameters) + "\" will be replaced with the actual value returned by the test.");
157250

test/libsolidity/util/TestFunctionCall.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class TestFunctionCall
8282
void setFailure(const bool _failure) { m_failure = _failure; }
8383
void setRawBytes(const bytes _rawBytes) { m_rawBytes = _rawBytes; }
8484
void setContractABI(Json::Value _contractABI) { m_contractABI = std::move(_contractABI); }
85-
std::vector<solidity::test::ExecutionFramework::log_record> logs()
85+
std::vector<solidity::test::ExecutionFramework::log_record> logs() const
8686
{
8787
return m_rawLogs;
8888
}
@@ -94,6 +94,18 @@ class TestFunctionCall
9494
{
9595
return m_consumedLogs;
9696
}
97+
bool hasUnconsumedLogs() const
98+
{
99+
return m_consumedLogs.size() < m_rawLogs.size();
100+
}
101+
std::vector<solidity::test::ExecutionFramework::log_record> unconsumedLogs() const
102+
{
103+
std::vector<solidity::test::ExecutionFramework::log_record> result;
104+
for (auto& log: m_rawLogs)
105+
if (m_consumedLogs.find(log.index) == m_consumedLogs.end())
106+
result.emplace_back(m_rawLogs[log.index]);
107+
return result;
108+
}
97109
void setPreviousCall(TestFunctionCall* _call)
98110
{
99111
m_previousCall = _call;
@@ -104,6 +116,14 @@ class TestFunctionCall
104116
}
105117

106118
private:
119+
struct EventInformation
120+
{
121+
std::string signature;
122+
std::vector<std::string> indexedTypes;
123+
std::vector<std::string> nonIndexedTypes;
124+
};
125+
EventInformation findEventSignature(solidity::test::ExecutionFramework::log_record const& log) const;
126+
107127
/// Tries to format the given `bytes`, applying the detected ABI types that have be set for each parameter.
108128
/// Throws if there's a mismatch in the size of `bytes` and the desired formats that are specified
109129
/// in the ABI type.

0 commit comments

Comments
 (0)