Merge pull request #7719 from ethereum/librarySignatuŕes

Add selector member to public and external library functions.
This commit is contained in:
chriseth 2019-11-20 18:43:14 +01:00 committed by GitHub
commit 8f2595957b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 291 additions and 0 deletions

View File

@ -1,6 +1,7 @@
### 0.5.14 (unreleased)
Language Features:
* Allow to obtain the selector of public or external library functions via a member ``.selector``.
Compiler Features:

View File

@ -208,6 +208,48 @@ Restrictions for libraries in comparison to contracts:
(These might be lifted at a later point.)
.. _library-selectors:
Function Signatures and Selectors in Libraries
==============================================
While external calls to public or external library functions are possible, the calling convention for such calls
is considered to be internal to Solidity and not the same as specified for the regular :ref:`contract ABI<ABI>`.
External library functions support more argument types than external contract functions, for example recursive structs
and storage pointers. For that reason, the function signatures used to compute the 4-byte selector are computed
following an internal naming schema and arguments of types not supported in the contract ABI use an internal encoding.
The following identifiers are used for the types in the signatures:
- Value types, non-storage ``string`` and non-storage ``bytes`` use the same identifiers as in the contract ABI.
- Non-storage array types follow the same convention as in the contract ABI, i.e. ``<type>[]`` for dynamic arrays and
``<type>[M]`` for fixed-size arrays of ``M`` elements.
- Non-storage structs are referred to by their fully qualified name, i.e. ``C.S`` for ``contract C { struct S { ... } }``.
- Storage pointer types use the type identifier of their corresponding non-storage type, but append a single space
followed by ``storage`` to it.
The argument encoding is the same as for the regular contract ABI, except for storage pointers, which are encoded as a
``uint256`` value referring to the storage slot to which they point.
Similarly to the contract ABI, the selector consists of the first four bytes of the Keccak256-hash of the signature.
Its value can be obtained from Solidity using the ``.selector`` member as follows:
::
pragma solidity >0.5.13 <0.7.0;
library L {
function f(uint256) external {}
}
contract C {
function g() public pure returns (bytes4) {
return L.f.selector;
}
}
.. _call-protection:
Call Protection For Libraries

View File

@ -2969,6 +2969,20 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
);
return members;
}
case Kind::DelegateCall:
{
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(m_declaration);
solAssert(functionDefinition, "");
solAssert(functionDefinition->visibility() != Declaration::Visibility::Private, "");
if (functionDefinition->visibility() != Declaration::Visibility::Internal)
{
auto const* contract = dynamic_cast<ContractDefinition const*>(m_declaration->scope());
solAssert(contract, "");
solAssert(contract->isLibrary(), "");
return {{"selector", TypeProvider::fixedBytes(4)}};
}
return {};
}
default:
return MemberList::MemberMap();
}

View File

@ -0,0 +1,32 @@
library L {
function f(uint256[] storage x) public pure returns (uint256) {
return 23;
}
}
contract C {
uint256[] y;
string x;
constructor() public { y.push(42); }
function f() public view returns (uint256) {
return L.f(y);
}
function g() public returns (bool, uint256) {
uint256 ys;
assembly { ys := y_slot }
(bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, ys));
return (success, success ? abi.decode(data,(uint256)) : 0);
}
function h() public returns (bool, uint256) {
uint256 ys;
assembly { ys := y_slot }
(bool success, bytes memory data) = address(L).call(abi.encodeWithSelector(L.f.selector, ys));
return (success, success ? abi.decode(data,(uint256)) : 0);
}
}
// ====
// EVMVersion: >homestead
// ----
// library: L
// f() -> 23
// g() -> true, 23
// h() -> true, 23

View File

@ -0,0 +1,32 @@
library L {
function f(uint256[] storage x) public view returns (uint256) {
return x.length;
}
}
contract C {
uint256[] y;
string x;
constructor() public { y.push(42); }
function f() public view returns (uint256) {
return L.f(y);
}
function g() public returns (bool, uint256) {
uint256 ys;
assembly { ys := y_slot }
(bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, ys));
return (success, success ? abi.decode(data,(uint256)) : 0);
}
function h() public returns (bool, uint256) {
uint256 ys;
assembly { ys := y_slot }
(bool success, bytes memory data) = address(L).call(abi.encodeWithSelector(L.f.selector, ys));
return (success, success ? abi.decode(data,(uint256)) : 0);
}
}
// ====
// EVMVersion: >homestead
// ----
// library: L
// f() -> 1
// g() -> true, 1
// h() -> true, 0 # this is bad - this should fail! #

View File

@ -0,0 +1,31 @@
library L {
function f(uint256[] storage x) public view returns (uint256) {
return 84;
}
}
contract C {
uint256[] y;
constructor() public { y.push(42); }
function f() public view returns (uint256) {
return L.f(y);
}
function g() public returns (bool, uint256) {
uint256 ys;
assembly { ys := y_slot }
(bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, ys));
return (success, success ? abi.decode(data,(uint256)) : 0);
}
function h() public returns (bool, uint256) {
uint256 ys;
assembly { ys := y_slot }
(bool success, bytes memory data) = address(L).call(abi.encodeWithSelector(L.f.selector, ys));
return (success, success ? abi.decode(data,(uint256)) : 0);
}
}
// ====
// EVMVersion: >homestead
// ----
// library: L
// f() -> 84
// g() -> true, 84
// h() -> true, 84

View File

@ -0,0 +1,31 @@
contract D {
uint public x;
constructor() public { x = 42; }
}
library L {
function f(D d) public view returns (uint256) {
return d.x();
}
}
contract C {
D d;
constructor() public { d = new D(); }
function f() public view returns (uint256) {
return L.f(d);
}
function g() public returns (bool, uint256) {
(bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, d));
return (success, success ? abi.decode(data,(uint256)) : 0);
}
function h() public returns (bool, uint256) {
(bool success, bytes memory data) = address(L).call(abi.encodeWithSelector(L.f.selector, d));
return (success, success ? abi.decode(data,(uint256)) : 0);
}
}
// ====
// EVMVersion: >homestead
// ----
// library: L
// f() -> 42
// g() -> true, 42
// h() -> true, 42

View File

@ -0,0 +1,30 @@
library L {
function f(uint256 x) external returns (uint) { return x; }
function g(uint256[] storage s) external returns (uint) { return s.length; }
function h(uint256[] memory m) public returns (uint) { return m.length; }
}
contract C {
uint256[] s;
constructor() public { while (s.length < 42) s.push(0); }
function f() public returns (bool, bool, uint256) {
(bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, 7));
return (L.f.selector == bytes4(keccak256("f(uint256)")), success, abi.decode(data, (uint256)));
}
function g() public returns (bool, bool, uint256) {
uint256 s_ptr;
assembly { s_ptr := s_slot }
(bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.g.selector, s_ptr));
return (L.g.selector == bytes4(keccak256("g(uint256[] storage)")), success, abi.decode(data, (uint256)));
}
function h() public returns (bool, bool, uint256) {
(bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.h.selector, new uint256[](23)));
return (L.h.selector == bytes4(keccak256("h(uint256[])")), success, abi.decode(data, (uint256)));
}
}
// ====
// EVMVersion: >homestead
// ----
// library: L
// f() -> true, true, 7
// g() -> true, true, 42
// h() -> true, true, 23

View File

@ -0,0 +1,27 @@
pragma experimental ABIEncoderV2;
library L {
struct S { uint256 a; }
function f(S storage s) external returns (uint) { return s.a; }
function g(S memory m) public returns (uint) { return m.a; }
}
contract C {
L.S s;
constructor() public { s.a = 42; }
function f() public returns (bool, bool, uint256) {
uint256 s_ptr;
assembly { s_ptr := s_slot }
(bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, s_ptr));
return (L.f.selector == bytes4(keccak256("f(L.S storage)")), success, abi.decode(data, (uint256)));
}
function g() public returns (bool, bool, uint256) {
(bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.g.selector, L.S(23)));
return (L.g.selector == bytes4(keccak256("g(L.S)")), success, abi.decode(data, (uint256)));
}
}
// ====
// EVMVersion: >homestead
// ----
// library: L
// f() -> true, true, 42
// g() -> true, true, 23

View File

@ -0,0 +1,13 @@
library L {
function f(uint256) external {}
function g(uint256[] storage) external {}
function h(uint256[] memory) public {}
}
contract C {
function f() public pure returns (bytes4 a, bytes4 b, bytes4 c, bytes4 d) {
a = L.f.selector;
b = L.g.selector;
c = L.h.selector;
d = L.h.selector;
}
}

View File

@ -0,0 +1,10 @@
library L {
function f(uint256) internal {}
}
contract C {
function f() public pure returns (bytes4) {
return L.f.selector;
}
}
// ----
// TypeError: (126-138): Member "selector" not found or not visible after argument-dependent lookup in function (uint256).

View File

@ -0,0 +1,8 @@
library L {
function f(uint256) private {}
function g(uint256) public returns (uint256) {
return f.selector;
}
}
// ----
// TypeError: (113-123): Member "selector" not found or not visible after argument-dependent lookup in function (uint256).

View File

@ -0,0 +1,10 @@
library L {
function f(uint256) private {}
}
contract C {
function f() public pure returns (bytes4) {
return L.f.selector;
}
}
// ----
// TypeError: (125-128): Member "f" not found or not visible after argument-dependent lookup in type(library L).

View File

@ -0,0 +1,10 @@
library L {
function f(uint256) external pure {}
function g(uint256) external view {}
}
contract C {
function f() public pure returns (bytes4, bytes4) {
return (L.f.selector, L.g.selector);
}
}
// ----