From b4d6f847b499da66c112b96c18d72d65b2eae172 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 2 May 2019 22:33:47 +0200 Subject: [PATCH] Events. --- libdevcore/StringUtils.h | 16 ++ libsolidity/codegen/YulUtilFunctions.cpp | 31 +++ libsolidity/codegen/YulUtilFunctions.h | 3 + .../codegen/ir/IRGenerationContext.cpp | 7 +- .../codegen/ir/IRGeneratorForStatements.cpp | 81 +++++++- test/libsolidity/SolidityEndToEndTest.cpp | 190 ++++++++++-------- 6 files changed, 237 insertions(+), 91 deletions(-) diff --git a/libdevcore/StringUtils.h b/libdevcore/StringUtils.h index 16c2fb158..16025078e 100644 --- a/libdevcore/StringUtils.h +++ b/libdevcore/StringUtils.h @@ -74,6 +74,22 @@ std::string joinHumanReadable return result; } +/// Joins collection of strings just like joinHumanReadable, but prepends the separator +/// unless the collection is empty. +template +std::string joinHumanReadablePrefixed +( + T const& _list, + std::string const& _separator = ", ", + std::string const& _lastSeparator = "" +) +{ + if (begin(_list) == end(_list)) + return {}; + else + return _separator + joinHumanReadable(_list, _separator, _lastSeparator); +} + /// Formats large numbers to be easily readable by humans. /// Returns decimal representation for smaller numbers; hex for large numbers. /// "Special" numbers, powers-of-two and powers-of-two minus 1, are returned in diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index b3872852f..91089115f 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -963,6 +963,37 @@ string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFail }); } +string YulUtilFunctions::packedHashFunction( + vector const& _givenTypes, + vector const& _targetTypes +) +{ + string functionName = string("packed_hashed_"); + for (auto const& t: _givenTypes) + functionName += t->identifier() + "_"; + functionName += "_to_"; + for (auto const& t: _targetTypes) + functionName += t->identifier() + "_"; + size_t sizeOnStack = 0; + for (Type const* t: _givenTypes) + sizeOnStack += t->sizeOnStack(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function () -> hash { + let pos := mload() + let end := (pos ) + hash := keccak256(pos, sub(end, pos)) + } + )"); + templ("functionName", functionName); + templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack)); + templ("comma", sizeOnStack > 0 ? "," : ""); + templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); + templ("packedEncode", ABIFunctions(m_evmVersion, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes)); + return templ.render(); + }); +} + string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix) { string result; diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 729710ac1..fd1e92ea4 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -26,6 +26,7 @@ #include #include +#include namespace dev { @@ -152,6 +153,8 @@ public: /// This is used for data decoded from external sources. std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); + std::string packedHashFunction(std::vector const& _givenTypes, std::vector const& _targetTypes); + /// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed /// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix, /// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix. diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index 9b7d3f169..3032fb94d 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -94,8 +94,11 @@ string IRGenerationContext::newYulVariable() string IRGenerationContext::variable(Expression const& _expression) { unsigned size = _expression.annotation().type->sizeOnStack(); - solUnimplementedAssert(size == 1, ""); - return "expr_" + to_string(_expression.id()); + string var = "expr_" + to_string(_expression.id()); + if (size == 1) + return var; + else + return YulUtilFunctions::suffixedVariableNameList(move(var) + "_", 1, 1 + size); } string IRGenerationContext::internalDispatch(size_t _in, size_t _out) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index a58783e53..32c574a7e 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,7 @@ #include #include +#include #include #include #include @@ -370,6 +372,65 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) ")\n"; break; } + case FunctionType::Kind::Event: + { + auto const& event = dynamic_cast(functionType->declaration()); + TypePointers paramTypes = functionType->parameterTypes(); + ABIFunctions abi(m_context.evmVersion(), m_context.functionCollector()); + + vector indexedArgs; + string nonIndexedArgs; + TypePointers nonIndexedArgTypes; + TypePointers nonIndexedParamTypes; + if (!event.isAnonymous()) + { + indexedArgs.emplace_back(m_context.newYulVariable()); + string signature = formatNumber(u256(h256::Arith(dev::keccak256(functionType->externalSignature())))); + m_code << "let " << indexedArgs.back() << " := " << signature << "\n"; + } + for (size_t i = 0; i < event.parameters().size(); ++i) + { + Expression const& arg = *arguments[i]; + if (event.parameters()[i]->isIndexed()) + { + string value; + indexedArgs.emplace_back(m_context.newYulVariable()); + if (auto const& referenceType = dynamic_cast(paramTypes[i])) + value = + m_utils.packedHashFunction({arg.annotation().type}, {referenceType}) + + "(" + + m_context.variable(arg) + + ")"; + else + value = expressionAsType(arg, *paramTypes[i]); + m_code << "let " << indexedArgs.back() << " := " << value << "\n"; + } + else + { + string vars = m_context.variable(arg); + if (!vars.empty()) + // In reverse because abi_encode expects it like that. + nonIndexedArgs = ", " + move(vars) + nonIndexedArgs; + nonIndexedArgTypes.push_back(arg.annotation().type); + nonIndexedParamTypes.push_back(paramTypes[i]); + } + } + solAssert(indexedArgs.size() <= 4, "Too many indexed arguments."); + Whiskers templ(R"({ + let := mload() + let := ( ) + (, sub(, ) ) + })"); + templ("pos", m_context.newYulVariable()); + templ("end", m_context.newYulVariable()); + templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); + templ("encode", abi.tupleEncoder(nonIndexedArgTypes, nonIndexedParamTypes)); + templ("nonIndexedArgs", nonIndexedArgs); + templ("log", "log" + to_string(indexedArgs.size())); + templ("indexedArgs", joinHumanReadablePrefixed(indexedArgs)); + m_code << templ.render(); + break; + } case FunctionType::Kind::Assert: case FunctionType::Kind::Require: { @@ -596,8 +657,26 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier) setLValue(_identifier, move(lvalue)); } + else if (auto contract = dynamic_cast(declaration)) + { + solUnimplementedAssert(!contract->isLibrary(), "Libraries not yet supported."); + } + else if (dynamic_cast(declaration)) + { + // no-op + } + else if (dynamic_cast(declaration)) + { + // no-op + } + else if (dynamic_cast(declaration)) + { + // no-op + } else - solUnimplemented("Identifier of type " + declaration->type()->toString() + " not implemented."); + { + solAssert(false, "Identifier type not expected in expression context."); + } } bool IRGeneratorForStatements::visit(Literal const& _literal) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 07bc1658b..f50c3d2f1 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -3677,17 +3677,19 @@ BOOST_AUTO_TEST_CASE(event_emit) } } )"; - compileAndRun(sourceCode); - u256 value(18); - u256 id(0x1234); - callContractFunctionWithValue("deposit(bytes32)", value, id); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK_EQUAL(h256(m_logs[0].data), h256(u256(value))); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 3); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,bytes32,uint256)"))); - BOOST_CHECK_EQUAL(m_logs[0].topics[1], h256(m_sender, h256::AlignRight)); - BOOST_CHECK_EQUAL(m_logs[0].topics[2], h256(id)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 value(18); + u256 id(0x1234); + callContractFunctionWithValue("deposit(bytes32)", value, id); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK_EQUAL(h256(m_logs[0].data), h256(u256(value))); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 3); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,bytes32,uint256)"))); + BOOST_CHECK_EQUAL(m_logs[0].topics[1], h256(m_sender, h256::AlignRight)); + BOOST_CHECK_EQUAL(m_logs[0].topics[2], h256(id)); + ) } BOOST_AUTO_TEST_CASE(event_no_arguments) @@ -3701,13 +3703,15 @@ BOOST_AUTO_TEST_CASE(event_no_arguments) } )"; - compileAndRun(sourceCode); - callContractFunction("deposit()"); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data.empty()); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + callContractFunction("deposit()"); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data.empty()); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); + ) } BOOST_AUTO_TEST_CASE(event_access_through_base_name_emit) @@ -3758,36 +3762,38 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) } } )"; - u160 const c_loggedAddress = m_contractAddress; + ALSO_VIA_YUL( + u160 const c_loggedAddress = m_contractAddress; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("deposit()"), encodeArgs(u256(1))); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data.empty()); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("deposit()"), encodeArgs(u256(1))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data.empty()); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); - ABI_CHECK(callContractFunction("deposit(address)", c_loggedAddress), encodeArgs(u256(2))); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)"))); + ABI_CHECK(callContractFunction("deposit(address)", c_loggedAddress), encodeArgs(u256(2))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + ABI_CHECK(m_logs[0].data, encodeArgs(c_loggedAddress)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)"))); - ABI_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)), encodeArgs(u256(3))); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, 100)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); + ABI_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)), encodeArgs(u256(3))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + ABI_CHECK(m_logs[0].data, encodeArgs(c_loggedAddress, 100)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); - ABI_CHECK(callContractFunction("deposit(address,bool)", c_loggedAddress, false), encodeArgs(u256(4))); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, false)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,bool)"))); + ABI_CHECK(callContractFunction("deposit(address,bool)", c_loggedAddress, false), encodeArgs(u256(4))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + ABI_CHECK(m_logs[0].data, encodeArgs(c_loggedAddress, false)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,bool)"))); + ) } BOOST_AUTO_TEST_CASE(events_with_same_name_inherited_emit) @@ -3817,29 +3823,31 @@ BOOST_AUTO_TEST_CASE(events_with_same_name_inherited_emit) } } )"; - u160 const c_loggedAddress = m_contractAddress; + ALSO_VIA_YUL( + u160 const c_loggedAddress = m_contractAddress; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("deposit()"), encodeArgs(u256(1))); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data.empty()); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("deposit()"), encodeArgs(u256(1))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data.empty()); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()"))); - ABI_CHECK(callContractFunction("deposit(address)", c_loggedAddress), encodeArgs(u256(1))); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)"))); + ABI_CHECK(callContractFunction("deposit(address)", c_loggedAddress), encodeArgs(u256(1))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)"))); - ABI_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)), encodeArgs(u256(1))); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, 100)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); + ABI_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)), encodeArgs(u256(1))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + ABI_CHECK(m_logs[0].data, encodeArgs(c_loggedAddress, 100)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)"))); + ) } BOOST_AUTO_TEST_CASE(event_anonymous) @@ -3852,9 +3860,11 @@ BOOST_AUTO_TEST_CASE(event_anonymous) } } )"; - compileAndRun(sourceCode); - callContractFunction("deposit()"); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 0); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + callContractFunction("deposit()"); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 0); + ) } BOOST_AUTO_TEST_CASE(event_anonymous_with_topics) @@ -3867,18 +3877,20 @@ BOOST_AUTO_TEST_CASE(event_anonymous_with_topics) } } )"; - compileAndRun(sourceCode); - u256 value(18); - u256 id(0x1234); - callContractFunctionWithValue("deposit(bytes32)", value, id); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs("abc")); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 4); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], h256(m_sender, h256::AlignRight)); - BOOST_CHECK_EQUAL(m_logs[0].topics[1], h256(id)); - BOOST_CHECK_EQUAL(m_logs[0].topics[2], h256(value)); - BOOST_CHECK_EQUAL(m_logs[0].topics[3], h256(2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 value(18); + u256 id(0x1234); + callContractFunctionWithValue("deposit(bytes32)", value, id); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs("abc")); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 4); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], h256(m_sender, h256::AlignRight)); + BOOST_CHECK_EQUAL(m_logs[0].topics[1], h256(id)); + BOOST_CHECK_EQUAL(m_logs[0].topics[2], h256(value)); + BOOST_CHECK_EQUAL(m_logs[0].topics[3], h256(2)); + ) } BOOST_AUTO_TEST_CASE(event_lots_of_data) @@ -3891,15 +3903,17 @@ BOOST_AUTO_TEST_CASE(event_lots_of_data) } } )"; - compileAndRun(sourceCode); - u256 value(18); - u256 id(0x1234); - callContractFunctionWithValue("deposit(bytes32)", value, id); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs((u160)m_sender, id, value, true)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,bytes32,uint256,bool)"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 value(18); + u256 id(0x1234); + callContractFunctionWithValue("deposit(bytes32)", value, id); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs((u160)m_sender, id, value, true)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,bytes32,uint256,bool)"))); + ) } BOOST_AUTO_TEST_CASE(event_really_lots_of_data)