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
14 changed files with 291 additions and 0 deletions
+1
View File
@@ -1,6 +1,7 @@
### 0.5.14 (unreleased) ### 0.5.14 (unreleased)
Language Features: Language Features:
* Allow to obtain the selector of public or external library functions via a member ``.selector``.
Compiler Features: Compiler Features:
+42
View File
@@ -208,6 +208,48 @@ Restrictions for libraries in comparison to contracts:
(These might be lifted at a later point.) (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:
Call Protection For Libraries Call Protection For Libraries
+14
View File
@@ -2969,6 +2969,20 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
); );
return members; 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: default:
return MemberList::MemberMap(); return MemberList::MemberMap();
} }
@@ -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
@@ -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! #
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
}
}
@@ -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).
@@ -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).
@@ -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).
@@ -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);
}
}
// ----