Skip to content

Commit c09dc61

Browse files
authored
Merge pull request #11050 from ethereum/isoltest-effects-events
[isoltest] Add support for events using call side-effects.
2 parents 6dd5bcd + 85e3fcb commit c09dc61

40 files changed

+663
-724
lines changed

test/ExecutionFramework.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,25 @@
2727

2828
#include <test/evmc/evmc.hpp>
2929

30+
#include <test/libsolidity/util/SoltestTypes.h>
31+
3032
#include <libsolutil/CommonIO.h>
3133
#include <libsolutil/FunctionSelector.h>
3234

3335
#include <liblangutil/Exceptions.h>
3436

3537
#include <boost/test/framework.hpp>
3638
#include <boost/algorithm/string/replace.hpp>
39+
#include <range/v3/range.hpp>
40+
#include <range/v3/view/transform.hpp>
3741

3842
#include <cstdlib>
3943

4044
using namespace std;
4145
using namespace solidity;
4246
using namespace solidity::util;
4347
using namespace solidity::test;
48+
using namespace solidity::frontend::test;
4449

4550
ExecutionFramework::ExecutionFramework():
4651
ExecutionFramework(solidity::test::CommonOptions::get().evmVersion(), solidity::test::CommonOptions::get().vmPaths)
@@ -282,3 +287,15 @@ bool ExecutionFramework::storageEmpty(h160 const& _addr) const
282287
}
283288
return true;
284289
}
290+
291+
vector<solidity::frontend::test::LogRecord> ExecutionFramework::recordedLogs() const
292+
{
293+
vector<LogRecord> logs;
294+
for (evmc::MockedHost::log_record const& logRecord: m_evmcHost->recorded_logs)
295+
logs.emplace_back(
296+
EVMHost::convertFromEVMC(logRecord.creator),
297+
bytes{logRecord.data.begin(), logRecord.data.end()},
298+
logRecord.topics | ranges::views::transform([](evmc::bytes32 _bytes) { return EVMHost::convertFromEVMC(_bytes); }) | ranges::to<vector>
299+
);
300+
return logs;
301+
}

test/ExecutionFramework.h

+12-5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939

4040
#include <boost/test/unit_test.hpp>
4141

42+
namespace solidity::frontend::test
43+
{
44+
struct LogRecord;
45+
} // namespace solidity::frontend::test
46+
4247
namespace solidity::test
4348
{
4449
using rational = boost::rational<bigint>;
@@ -247,6 +252,12 @@ class ExecutionFramework
247252
return m_sender;
248253
}
249254

255+
size_t numLogs() const;
256+
size_t numLogTopics(size_t _logIdx) const;
257+
util::h256 logTopic(size_t _logIdx, size_t _topicIdx) const;
258+
util::h160 logAddress(size_t _logIdx) const;
259+
bytes logData(size_t _logIdx) const;
260+
250261
private:
251262
template <class CppFunction, class... Args>
252263
auto callCppAndEncodeResult(CppFunction const& _cppFunction, Args const&... _arguments)
@@ -278,11 +289,7 @@ class ExecutionFramework
278289
bool storageEmpty(util::h160 const& _addr) const;
279290
bool addressHasCode(util::h160 const& _addr) const;
280291

281-
size_t numLogs() const;
282-
size_t numLogTopics(size_t _logIdx) const;
283-
util::h256 logTopic(size_t _logIdx, size_t _topicIdx) const;
284-
util::h160 logAddress(size_t _logIdx) const;
285-
bytes logData(size_t _logIdx) const;
292+
std::vector<frontend::test::LogRecord> recordedLogs() const;
286293

287294
langutil::EVMVersion m_evmVersion;
288295
solidity::frontend::RevertStrings m_revertStrings = solidity::frontend::RevertStrings::Default;

test/libsolidity/SemanticTest.cpp

+106-2
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,9 @@ map<string, Builtin> SemanticTest::makeBuiltins()
178178
};
179179
}
180180

181-
182181
vector<SideEffectHook> SemanticTest::makeSideEffectHooks() const
183182
{
183+
using namespace std::placeholders;
184184
return {
185185
[](FunctionCall const& _call) -> vector<string>
186186
{
@@ -192,7 +192,111 @@ vector<SideEffectHook> SemanticTest::makeSideEffectHooks() const
192192
return result;
193193
}
194194
return {};
195-
}};
195+
},
196+
bind(&SemanticTest::eventSideEffectHook, this, _1)
197+
};
198+
}
199+
200+
string SemanticTest::formatEventParameter(optional<AnnotatedEventSignature> _signature, bool _indexed, size_t _index, bytes const& _data)
201+
{
202+
auto isPrintableASCII = [](bytes const& s)
203+
{
204+
bool zeroes = true;
205+
for (auto c: s)
206+
{
207+
if (static_cast<unsigned>(c) != 0x00)
208+
{
209+
zeroes = false;
210+
if (static_cast<unsigned>(c) <= 0x1f || static_cast<unsigned>(c) >= 0x7f)
211+
return false;
212+
} else
213+
break;
214+
}
215+
return !zeroes;
216+
};
217+
218+
ABIType abiType(ABIType::Type::Hex);
219+
if (isPrintableASCII(_data))
220+
abiType = ABIType(ABIType::Type::String);
221+
if (_signature.has_value())
222+
{
223+
vector<string> const& types = _indexed ? _signature->indexedTypes : _signature->nonIndexedTypes;
224+
if (_index < types.size())
225+
{
226+
if (types.at(_index) == "bool")
227+
abiType = ABIType(ABIType::Type::Boolean);
228+
}
229+
}
230+
return BytesUtils::formatBytes(_data, abiType);
231+
}
232+
233+
vector<string> SemanticTest::eventSideEffectHook(FunctionCall const&) const
234+
{
235+
vector<string> sideEffects;
236+
vector<LogRecord> recordedLogs = ExecutionFramework::recordedLogs();
237+
for (LogRecord const& log: recordedLogs)
238+
{
239+
optional<AnnotatedEventSignature> eventSignature;
240+
if (!log.topics.empty())
241+
eventSignature = matchEvent(log.topics[0]);
242+
stringstream sideEffect;
243+
sideEffect << "emit ";
244+
if (eventSignature.has_value())
245+
sideEffect << eventSignature.value().signature;
246+
else
247+
sideEffect << "<anonymous>";
248+
249+
if (m_contractAddress != log.creator)
250+
sideEffect << " from 0x" << log.creator;
251+
252+
vector<string> eventStrings;
253+
size_t index{0};
254+
for (h256 const& topic: log.topics)
255+
{
256+
if (!eventSignature.has_value() || index != 0)
257+
eventStrings.push_back("#" + formatEventParameter(eventSignature, true, index, topic.asBytes()));
258+
++index;
259+
}
260+
261+
soltestAssert(log.data.size() % 32 == 0, "");
262+
for (size_t index = 0; index < log.data.size() / 32; ++index)
263+
{
264+
auto begin = log.data.begin() + static_cast<long>(index * 32);
265+
bytes const& data = bytes{begin, begin + 32};
266+
eventStrings.emplace_back(formatEventParameter(eventSignature, false, index, data));
267+
}
268+
269+
if (!eventStrings.empty())
270+
sideEffect << ": ";
271+
sideEffect << joinHumanReadable(eventStrings);
272+
sideEffects.emplace_back(sideEffect.str());
273+
}
274+
return sideEffects;
275+
}
276+
277+
optional<AnnotatedEventSignature> SemanticTest::matchEvent(util::h256 const& hash) const
278+
{
279+
optional<AnnotatedEventSignature> result;
280+
for (string& contractName: m_compiler.contractNames())
281+
{
282+
ContractDefinition const& contract = m_compiler.contractDefinition(contractName);
283+
for (EventDefinition const* event: contract.events())
284+
{
285+
FunctionTypePointer eventFunctionType = event->functionType(true);
286+
if (!event->isAnonymous() && keccak256(eventFunctionType->externalSignature()) == hash)
287+
{
288+
AnnotatedEventSignature eventInfo;
289+
eventInfo.signature = eventFunctionType->externalSignature();
290+
for (auto const& param: event->parameters())
291+
if (param->isIndexed())
292+
eventInfo.indexedTypes.emplace_back(param->type()->toString(true));
293+
else
294+
eventInfo.nonIndexedTypes.emplace_back(param->type()->toString(true));
295+
result = eventInfo;
296+
}
297+
}
298+
}
299+
return result;
196300
}
197301

198302
TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)

test/libsolidity/SemanticTest.h

+10
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@
3030
namespace solidity::frontend::test
3131
{
3232

33+
struct AnnotatedEventSignature
34+
{
35+
std::string signature;
36+
std::vector<std::string> indexedTypes;
37+
std::vector<std::string> nonIndexedTypes;
38+
};
39+
3340
/**
3441
* Class that represents a semantic test (or end-to-end test) and allows running it as part of the
3542
* boost unit test environment or isoltest. It reads the Solidity source and an additional comment
@@ -82,6 +89,9 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
8289
bool checkGasCostExpectation(TestFunctionCall& io_test, bool _compileViaYul) const;
8390
std::map<std::string, Builtin> makeBuiltins();
8491
std::vector<SideEffectHook> makeSideEffectHooks() const;
92+
std::vector<std::string> eventSideEffectHook(FunctionCall const&) const;
93+
std::optional<AnnotatedEventSignature> matchEvent(util::h256 const& hash) const;
94+
static std::string formatEventParameter(std::optional<AnnotatedEventSignature> _signature, bool _indexed, size_t _index, bytes const& _data);
8595
SourceMap m_sources;
8696
std::size_t m_lineOffset;
8797
std::vector<TestFunctionCall> m_tests;

0 commit comments

Comments
 (0)