mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[isoltest] Add support for events using call side-effects.
This commit is contained in:
parent
aec20f0038
commit
ec86e3e9ae
@ -27,6 +27,8 @@
|
||||
|
||||
#include <test/evmc/evmc.hpp>
|
||||
|
||||
#include <test/libsolidity/util/SoltestTypes.h>
|
||||
|
||||
#include <libsolutil/CommonIO.h>
|
||||
#include <libsolutil/FunctionSelector.h>
|
||||
|
||||
@ -34,6 +36,8 @@
|
||||
|
||||
#include <boost/test/framework.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <range/v3/range.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
@ -41,6 +45,7 @@ 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)
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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>;
|
||||
@ -247,6 +252,12 @@ public:
|
||||
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)
|
||||
@ -278,11 +289,7 @@ protected:
|
||||
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;
|
||||
|
@ -178,9 +178,9 @@ map<string, Builtin> SemanticTest::makeBuiltins()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
vector<SideEffectHook> SemanticTest::makeSideEffectHooks() const
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
return {
|
||||
[](FunctionCall const& _call) -> vector<string>
|
||||
{
|
||||
@ -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)
|
||||
|
@ -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
|
||||
@ -82,6 +89,9 @@ private:
|
||||
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;
|
||||
|
@ -0,0 +1,11 @@
|
||||
contract ClientReceipt {
|
||||
event Deposit(uint256 indexed _from, bytes32 indexed _id, uint _value) anonymous;
|
||||
function deposit(bytes32 _id) public payable {
|
||||
emit Deposit(0x2012159ca6b6372f102c535a4814d13a00bfc5568ddfd72151364061b00355d1, _id, msg.value); // 0x2012159c -> 'Deposit(uint256,bytes32,uint256)'
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// deposit(bytes32), 18 wei: 0x1234 ->
|
||||
// ~ emit <anonymous>: #0x2012159ca6b6372f102c535a4814d13a00bfc5568ddfd72151364061b00355d1, #0x1234, 0x12
|
24
test/libsolidity/semanticTests/events/event_string.sol
Normal file
24
test/libsolidity/semanticTests/events/event_string.sol
Normal file
@ -0,0 +1,24 @@
|
||||
contract C {
|
||||
string x;
|
||||
uint[4] y;
|
||||
event E(string indexed r, uint[4] indexed t);
|
||||
function deposit() public {
|
||||
for (uint i = 0; i < 90; i++)
|
||||
bytes(x).push(0);
|
||||
for (uint8 i = 0; i < 90; i++)
|
||||
bytes(x)[i] = bytes1(i);
|
||||
y[0] = 4;
|
||||
y[1] = 5;
|
||||
y[2] = 6;
|
||||
y[3] = 7;
|
||||
emit E(x, y);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// deposit() ->
|
||||
// ~ emit E(string,uint256[4]): #0xa7fb06bb999a5eb9aff9e0779953f4e1e4ce58044936c2f51c7fb879b85c08bd, #0xe755d8cc1a8cde16a2a31160dcd8017ac32d7e2f13215b29a23cdae40a78aa81
|
||||
// gas irOptimized: 792278
|
||||
// gas legacy: 390742
|
||||
// gas legacyOptimized: 930774
|
@ -197,12 +197,14 @@ contract DepositContract is IDepositContract, ERC165 {
|
||||
// gas legacyOptimized: 122798
|
||||
// get_deposit_count() -> 0x20, 8, 0
|
||||
// deposit(bytes,bytes,bytes,bytes32), 1 ether: 0x80, 0xe0, 0x120, 0xaa4a8d0b7d9077248630f1a4701ae9764e42271d7f22b7838778411857fd349e, 0x30, 0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f73292, 0x67a8811c397529dac52ae1342ba58c9500000000000000000000000000000000, 0x20, 0x00f50428677c60f997aadeab24aabf7fceaef491c96a52b463ae91f95611cf71, 0x60, 0xa29d01cc8c6296a8150e515b5995390ef841dc18948aa3e79be6d7c1851b4cbb, 0x5d6ff49fa70b9c782399506a22a85193151b9b691245cebafd2063012443c132, 0x4b6c36debaedefb7b2d71b0503ffdc00150aaffd42e63358238ec888901738b8 -> # txhash: 0x7085c586686d666e8bb6e9477a0f0b09565b2060a11f1c4209d3a52295033832 #
|
||||
// ~ emit DepositEvent(bytes,bytes,bytes,bytes,bytes): 0xa0, 0x0100, 0x0140, 0x0180, 0x0200, 0x30, 0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f73292, 0x67a8811c397529dac52ae1342ba58c9500000000000000000000000000000000, 0x20, 0xf50428677c60f997aadeab24aabf7fceaef491c96a52b463ae91f95611cf71, 0x08, 0xca9a3b00000000000000000000000000000000000000000000000000000000, 0x60, 0xa29d01cc8c6296a8150e515b5995390ef841dc18948aa3e79be6d7c1851b4cbb, 0x5d6ff49fa70b9c782399506a22a85193151b9b691245cebafd2063012443c132, 0x4b6c36debaedefb7b2d71b0503ffdc00150aaffd42e63358238ec888901738b8, 0x08, 0x00
|
||||
// get_deposit_root() -> 0x2089653123d9c721215120b6db6738ba273bbc5228ac093b1f983badcdc8a438
|
||||
// gas irOptimized: 127486
|
||||
// gas legacy: 150475
|
||||
// gas legacyOptimized: 122811
|
||||
// get_deposit_count() -> 0x20, 8, 0x0100000000000000000000000000000000000000000000000000000000000000
|
||||
// deposit(bytes,bytes,bytes,bytes32), 32 ether: 0x80, 0xe0, 0x120, 0xdbd986dc85ceb382708cf90a3500f500f0a393c5ece76963ac3ed72eccd2c301, 0x30, 0xb2ce0f79f90e7b3a113ca5783c65756f96c4b4673c2b5c1eb4efc22280259441, 0x06d601211e8866dc5b50dc48a244dd7c00000000000000000000000000000000, 0x20, 0x00344b6c73f71b11c56aba0d01b7d8ad83559f209d0a4101a515f6ad54c89771, 0x60, 0x945caaf82d18e78c033927d51f452ebcd76524497b91d7a11219cb3db6a1d369, 0x7595fc095ce489e46b2ef129591f2f6d079be4faaf345a02c5eb133c072e7c56, 0x0c6c3617eee66b4b878165c502357d49485326bc6b31bc96873f308c8f19c09d -> # txhash: 0x404d8e109822ce448e68f45216c12cb051b784d068fbe98317ab8e50c58304ac #
|
||||
// ~ emit DepositEvent(bytes,bytes,bytes,bytes,bytes): 0xa0, 0x0100, 0x0140, 0x0180, 0x0200, 0x30, 0xb2ce0f79f90e7b3a113ca5783c65756f96c4b4673c2b5c1eb4efc22280259441, 0x06d601211e8866dc5b50dc48a244dd7c00000000000000000000000000000000, 0x20, 0x344b6c73f71b11c56aba0d01b7d8ad83559f209d0a4101a515f6ad54c89771, 0x08, 0x40597307000000000000000000000000000000000000000000000000000000, 0x60, 0x945caaf82d18e78c033927d51f452ebcd76524497b91d7a11219cb3db6a1d369, 0x7595fc095ce489e46b2ef129591f2f6d079be4faaf345a02c5eb133c072e7c56, 0x0c6c3617eee66b4b878165c502357d49485326bc6b31bc96873f308c8f19c09d, 0x08, 0x0100000000000000000000000000000000000000000000000000000000000000
|
||||
// get_deposit_root() -> 0x40255975859377d912c53aa853245ebd939bdd2b33a28e084babdcc1ed8238ee
|
||||
// gas irOptimized: 127486
|
||||
// gas legacy: 150475
|
||||
|
@ -15,3 +15,4 @@ contract C {
|
||||
// ----
|
||||
// library: L
|
||||
// f() ->
|
||||
// ~ emit Ev((uint256)): 0x01
|
||||
|
@ -98,7 +98,10 @@ contract ERC20 {
|
||||
// ----
|
||||
// totalSupply() -> 20
|
||||
// transfer(address,uint256): 2, 5 -> true
|
||||
// ~ emit Transfer(address,address,uint256): #0x1212121212121212121212121212120000000012, #0x02, 0x05
|
||||
// decreaseAllowance(address,uint256): 2, 0 -> true
|
||||
// ~ emit Approval(address,address,uint256): #0x1212121212121212121212121212120000000012, #0x02, 0x00
|
||||
// decreaseAllowance(address,uint256): 2, 1 -> FAILURE, hex"4e487b71", 0x11
|
||||
// transfer(address,uint256): 2, 14 -> true
|
||||
// ~ emit Transfer(address,address,uint256): #0x1212121212121212121212121212120000000012, #0x02, 0x0e
|
||||
// transfer(address,uint256): 2, 2 -> FAILURE, hex"4e487b71", 0x11
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
#include <test/ExecutionFramework.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::frontend::test
|
||||
{
|
||||
|
||||
@ -311,4 +313,24 @@ struct FunctionCall
|
||||
using Builtin = std::function<std::optional<bytes>(FunctionCall const&)>;
|
||||
using SideEffectHook = std::function<std::vector<std::string>(FunctionCall const&)>;
|
||||
|
||||
struct LogRecord
|
||||
{
|
||||
util::h160 creator;
|
||||
bytes data;
|
||||
std::vector<util::h256> topics;
|
||||
|
||||
LogRecord(util::h160 _creator, bytes _data, std::vector<util::h256> _topics):
|
||||
creator(std::move(_creator)), data(std::move(_data)), topics(std::move(_topics)) {}
|
||||
|
||||
bool operator==(LogRecord const& other) const noexcept
|
||||
{
|
||||
return creator == other.creator && data == other.data && topics == other.topics;
|
||||
}
|
||||
|
||||
bool operator!=(LogRecord const& other) const noexcept
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -196,8 +196,6 @@ string TestFunctionCall::format(
|
||||
}
|
||||
}
|
||||
|
||||
stream << formatGasExpectations(_linePrefix, _renderMode == RenderMode::ExpectedValuesActualGas, _interactivePrint);
|
||||
|
||||
vector<string> sideEffects;
|
||||
if (_renderMode == RenderMode::ExpectedValuesExpectedGas || _renderMode == RenderMode::ExpectedValuesActualGas)
|
||||
sideEffects = m_call.expectedSideEffects;
|
||||
@ -214,6 +212,8 @@ string TestFunctionCall::format(
|
||||
stream << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
stream << formatGasExpectations(_linePrefix, _renderMode == RenderMode::ExpectedValuesActualGas, _interactivePrint);
|
||||
};
|
||||
|
||||
formatOutput(m_call.displayMode == FunctionCall::DisplayMode::SingleLine);
|
||||
|
Loading…
Reference in New Issue
Block a user