Skip to content
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
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
### 0.4.17 (unreleased)

Features:
* Code Generator: Added ``.selector`` member on external function types to retrieve their signature.
* Optimizer: Add new optimization step to remove unused ``JUMPDEST``s.
* Type Checker: Display helpful warning for unused function arguments/return parameters.
* Type Checker: Do not show the same error multiple times for events.
Expand Down
2 changes: 2 additions & 0 deletions docs/abi-spec.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ We assume the interface functions of a contract are strongly typed, known at com

This specification does not address contracts whose interface is dynamic or otherwise known only at run-time. Should these cases become important they can be adequately handled as facilities built within the Ethereum ecosystem.

.. _abi_function_selector:

Function Selector
=================

Expand Down
11 changes: 11 additions & 0 deletions docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,17 @@ Note that public functions of the current contract can be used both as an
internal and as an external function. To use ``f`` as an internal function,
just use ``f``, if you want to use its external form, use ``this.f``.

Additionally, public (or external) functions also have a special member called ``selector``,
which returns the :ref:`ABI function selector <abi_function_selector>`::

pragma solidity ^0.4.0;

contract Selector {
function f() returns (bytes4) {
return this.f.selector;
}
}

Example that shows how to use internal function types::

pragma solidity ^0.4.5;
Expand Down
5 changes: 5 additions & 0 deletions libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2436,6 +2436,11 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
case Kind::BareDelegateCall:
{
MemberList::MemberMap members;
if (m_kind == Kind::External)
members.push_back(MemberList::Member(
"selector",
make_shared<FixedBytesType>(4)
));
if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall)
{
if (isPayable())
Expand Down
9 changes: 8 additions & 1 deletion libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1068,7 +1068,14 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(false, "Invalid member access to integer");
break;
case Type::Category::Function:
solAssert(!!_memberAccess.expression().annotation().type->memberType(member),
if (member == "selector")
{
m_context << Instruction::SWAP1 << Instruction::POP;
/// need to store store it as bytes4
utils().leftShiftNumberOnStack(224);
}
else
solAssert(!!_memberAccess.expression().annotation().type->memberType(member),
"Invalid member access to function.");
break;
case Type::Category::Magic:
Expand Down
24 changes: 24 additions & 0 deletions test/libsolidity/SolidityEndToEndTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10051,6 +10051,30 @@ BOOST_AUTO_TEST_CASE(delegatecall_return_value)
BOOST_CHECK(callContractFunction("get_delegated()") == encodeArgs(u256(1)));
}

BOOST_AUTO_TEST_CASE(function_types_sig)
{
char const* sourceCode = R"(
contract C {
function f() returns (bytes4) {
return this.f.selector;
}
function g() returns (bytes4) {
function () external returns (bytes4) fun = this.f;
return fun.selector;
}
function h() returns (bytes4) {
function () external returns (bytes4) fun = this.f;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works now, but not sure we are doing the right thing, since the function signature of this function type can be different depending on what function was assigned to it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but the correct type signature has to be there, since we also have to know it when we want to call the function. What are your worries?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind.

var funvar = fun;
return funvar.selector;
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(asString(FixedHash<4>(dev::keccak256("f()")).asBytes())));
BOOST_CHECK(callContractFunction("g()") == encodeArgs(asString(FixedHash<4>(dev::keccak256("f()")).asBytes())));
BOOST_CHECK(callContractFunction("h()") == encodeArgs(asString(FixedHash<4>(dev::keccak256("f()")).asBytes())));
}

BOOST_AUTO_TEST_SUITE_END()

}
Expand Down
81 changes: 81 additions & 0 deletions test/libsolidity/SolidityNameAndTypeResolution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6285,6 +6285,87 @@ BOOST_AUTO_TEST_CASE(modifiers_access_storage_pointer)
CHECK_SUCCESS_NO_WARNINGS(text);
}

BOOST_AUTO_TEST_CASE(function_types_sig)
{
char const* text = R"(
contract C {
function f() returns (bytes4) {
return f.selector;
}
}
)";
CHECK_ERROR(text, TypeError, "Member \"selector\" not found");
text = R"(
contract C {
function g() internal {
}
function f() returns (bytes4) {
return g.selector;
}
}
)";
CHECK_ERROR(text, TypeError, "Member \"selector\" not found");
text = R"(
contract C {
function f() returns (bytes4) {
function () g;
return g.selector;
}
}
)";
CHECK_ERROR(text, TypeError, "Member \"selector\" not found");
text = R"(
contract C {
function f() returns (bytes4) {
return this.f.selector;
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
text = R"(
contract C {
function f() external returns (bytes4) {
return this.f.selector;
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
text = R"(
contract C {
function h() external {
}
function f() external returns (bytes4) {
var g = this.h;
return g.selector;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is not detected yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chriseth if you know a quick way to solve this, please do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
text = R"(
contract C {
function h() external {
}
function f() external returns (bytes4) {
function () external g = this.h;
return g.selector;
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
text = R"(
contract C {
function h() external {
}
function f() external returns (bytes4) {
function () external g = this.h;
var i = g;
return i.selector;
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}

BOOST_AUTO_TEST_CASE(using_this_in_constructor)
{
char const* text = R"(
Expand Down