Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[isoltest] Add support for events using call side-effects. #11050

Merged
merged 2 commits into from
May 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions test/ExecutionFramework.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,25 @@

#include <test/evmc/evmc.hpp>

#include <test/libsolidity/util/SoltestTypes.h>

#include <libsolutil/CommonIO.h>
#include <libsolutil/FunctionSelector.h>

#include <liblangutil/Exceptions.h>

#include <boost/test/framework.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <range/v3/range.hpp>
#include <range/v3/view/transform.hpp>

#include <cstdlib>

using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::test;
using namespace solidity::frontend::test;

ExecutionFramework::ExecutionFramework():
ExecutionFramework(solidity::test::CommonOptions::get().evmVersion(), solidity::test::CommonOptions::get().vmPaths)
Expand Down Expand Up @@ -282,3 +287,15 @@ bool ExecutionFramework::storageEmpty(h160 const& _addr) const
}
return true;
}

vector<solidity::frontend::test::LogRecord> ExecutionFramework::recordedLogs() const
{
vector<LogRecord> logs;
for (evmc::MockedHost::log_record const& logRecord: m_evmcHost->recorded_logs)
logs.emplace_back(
EVMHost::convertFromEVMC(logRecord.creator),
bytes{logRecord.data.begin(), logRecord.data.end()},
logRecord.topics | ranges::views::transform([](evmc::bytes32 _bytes) { return EVMHost::convertFromEVMC(_bytes); }) | ranges::to<vector>
);
return logs;
}
17 changes: 12 additions & 5 deletions test/ExecutionFramework.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@

#include <boost/test/unit_test.hpp>

namespace solidity::frontend::test
{
struct LogRecord;
} // namespace solidity::frontend::test

namespace solidity::test
{
using rational = boost::rational<bigint>;
Expand Down Expand Up @@ -247,6 +252,12 @@ class ExecutionFramework
return m_sender;
}

size_t numLogs() const;
size_t numLogTopics(size_t _logIdx) const;
util::h256 logTopic(size_t _logIdx, size_t _topicIdx) const;
util::h160 logAddress(size_t _logIdx) const;
bytes logData(size_t _logIdx) const;

private:
template <class CppFunction, class... Args>
auto callCppAndEncodeResult(CppFunction const& _cppFunction, Args const&... _arguments)
Expand Down Expand Up @@ -278,11 +289,7 @@ class ExecutionFramework
bool storageEmpty(util::h160 const& _addr) const;
bool addressHasCode(util::h160 const& _addr) const;

size_t numLogs() const;
size_t numLogTopics(size_t _logIdx) const;
util::h256 logTopic(size_t _logIdx, size_t _topicIdx) const;
util::h160 logAddress(size_t _logIdx) const;
bytes logData(size_t _logIdx) const;
std::vector<frontend::test::LogRecord> recordedLogs() const;

langutil::EVMVersion m_evmVersion;
solidity::frontend::RevertStrings m_revertStrings = solidity::frontend::RevertStrings::Default;
Expand Down
108 changes: 106 additions & 2 deletions test/libsolidity/SemanticTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ map<string, Builtin> SemanticTest::makeBuiltins()
};
}


vector<SideEffectHook> SemanticTest::makeSideEffectHooks() const
{
using namespace std::placeholders;
return {
[](FunctionCall const& _call) -> vector<string>
{
Expand All @@ -192,7 +192,111 @@ vector<SideEffectHook> SemanticTest::makeSideEffectHooks() const
return result;
}
return {};
}};
},
bind(&SemanticTest::eventSideEffectHook, this, _1)
};
}

string SemanticTest::formatEventParameter(optional<AnnotatedEventSignature> _signature, bool _indexed, size_t _index, bytes const& _data)
{
auto isPrintableASCII = [](bytes const& s)
{
bool zeroes = true;
for (auto c: s)
{
if (static_cast<unsigned>(c) != 0x00)
{
zeroes = false;
if (static_cast<unsigned>(c) <= 0x1f || static_cast<unsigned>(c) >= 0x7f)
return false;
} else
break;
}
return !zeroes;
};

ABIType abiType(ABIType::Type::Hex);
if (isPrintableASCII(_data))
abiType = ABIType(ABIType::Type::String);
if (_signature.has_value())
{
vector<string> const& types = _indexed ? _signature->indexedTypes : _signature->nonIndexedTypes;
if (_index < types.size())
{
if (types.at(_index) == "bool")
abiType = ABIType(ABIType::Type::Boolean);
}
}
return BytesUtils::formatBytes(_data, abiType);
}

vector<string> SemanticTest::eventSideEffectHook(FunctionCall const&) const
{
vector<string> sideEffects;
vector<LogRecord> recordedLogs = ExecutionFramework::recordedLogs();
for (LogRecord const& log: recordedLogs)
{
optional<AnnotatedEventSignature> eventSignature;
if (!log.topics.empty())
eventSignature = matchEvent(log.topics[0]);
stringstream sideEffect;
sideEffect << "emit ";
if (eventSignature.has_value())
sideEffect << eventSignature.value().signature;
else
sideEffect << "<anonymous>";

if (m_contractAddress != log.creator)
sideEffect << " from 0x" << log.creator;

vector<string> eventStrings;
size_t index{0};
for (h256 const& topic: log.topics)
{
if (!eventSignature.has_value() || index != 0)
eventStrings.push_back("#" + formatEventParameter(eventSignature, true, index, topic.asBytes()));
++index;
}

soltestAssert(log.data.size() % 32 == 0, "");
for (size_t index = 0; index < log.data.size() / 32; ++index)
{
auto begin = log.data.begin() + static_cast<long>(index * 32);
bytes const& data = bytes{begin, begin + 32};
eventStrings.emplace_back(formatEventParameter(eventSignature, false, index, data));
}

if (!eventStrings.empty())
sideEffect << ": ";
sideEffect << joinHumanReadable(eventStrings);
sideEffects.emplace_back(sideEffect.str());
}
return sideEffects;
}

optional<AnnotatedEventSignature> SemanticTest::matchEvent(util::h256 const& hash) const
{
optional<AnnotatedEventSignature> result;
for (string& contractName: m_compiler.contractNames())
{
ContractDefinition const& contract = m_compiler.contractDefinition(contractName);
for (EventDefinition const* event: contract.events())
{
FunctionTypePointer eventFunctionType = event->functionType(true);
if (!event->isAnonymous() && keccak256(eventFunctionType->externalSignature()) == hash)
{
AnnotatedEventSignature eventInfo;
eventInfo.signature = eventFunctionType->externalSignature();
for (auto const& param: event->parameters())
if (param->isIndexed())
eventInfo.indexedTypes.emplace_back(param->type()->toString(true));
else
eventInfo.nonIndexedTypes.emplace_back(param->type()->toString(true));
result = eventInfo;
}
}
}
return result;
}

TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
Expand Down
10 changes: 10 additions & 0 deletions test/libsolidity/SemanticTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
namespace solidity::frontend::test
{

struct AnnotatedEventSignature
{
std::string signature;
std::vector<std::string> indexedTypes;
std::vector<std::string> nonIndexedTypes;
};

/**
* Class that represents a semantic test (or end-to-end test) and allows running it as part of the
* boost unit test environment or isoltest. It reads the Solidity source and an additional comment
Expand Down Expand Up @@ -82,6 +89,9 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
bool checkGasCostExpectation(TestFunctionCall& io_test, bool _compileViaYul) const;
std::map<std::string, Builtin> makeBuiltins();
std::vector<SideEffectHook> makeSideEffectHooks() const;
std::vector<std::string> eventSideEffectHook(FunctionCall const&) const;
std::optional<AnnotatedEventSignature> matchEvent(util::h256 const& hash) const;
static std::string formatEventParameter(std::optional<AnnotatedEventSignature> _signature, bool _indexed, size_t _index, bytes const& _data);
SourceMap m_sources;
std::size_t m_lineOffset;
std::vector<TestFunctionCall> m_tests;
Expand Down
Loading