Skip to content

Commit 365bf69

Browse files
authored
MultichainRegistry: new code pattern (#285)
* MultichainRegistry: new code pattern * rename logic contracts * docs * v3.3.0-3 * v3.3.0-4 * IEntrypoint interface * update with new plugin design * v3.3.0-5 * update multichain registry with new plugin pattern * remove duplicate interface * forge update
1 parent 3f61125 commit 365bf69

29 files changed

+2230
-44
lines changed

contracts/extension/plugin/Router.sol

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ abstract contract Router is Multicall, ERC165, IRouter {
2929
State variables
3030
//////////////////////////////////////////////////////////////*/
3131

32-
address public immutable functionMap;
32+
address public immutable pluginMap;
3333

3434
/*///////////////////////////////////////////////////////////////
3535
Constructor + initializer logic
3636
//////////////////////////////////////////////////////////////*/
3737

38-
constructor(address _functionMap) {
39-
functionMap = _functionMap;
38+
constructor(address _pluginMap) {
39+
pluginMap = _pluginMap;
4040
}
4141

4242
/*///////////////////////////////////////////////////////////////
@@ -57,7 +57,7 @@ abstract contract Router is Multicall, ERC165, IRouter {
5757
fallback() external payable virtual {
5858
address _pluginAddress = _getPluginForFunction(msg.sig);
5959
if (_pluginAddress == address(0)) {
60-
_pluginAddress = IPluginMap(functionMap).getPluginForFunction(msg.sig);
60+
_pluginAddress = IPluginMap(pluginMap).getPluginForFunction(msg.sig);
6161
}
6262
_delegate(_pluginAddress);
6363
}
@@ -122,15 +122,15 @@ abstract contract Router is Multicall, ERC165, IRouter {
122122
function getPluginForFunction(bytes4 _selector) public view returns (address) {
123123
address pluginAddress = _getPluginForFunction(_selector);
124124

125-
return pluginAddress != address(0) ? pluginAddress : IPluginMap(functionMap).getPluginForFunction(_selector);
125+
return pluginAddress != address(0) ? pluginAddress : IPluginMap(pluginMap).getPluginForFunction(_selector);
126126
}
127127

128128
/// @dev View all funtionality as list of function signatures.
129129
function getAllFunctionsOfPlugin(address _pluginAddress) external view returns (bytes4[] memory registered) {
130130
RouterStorage.Data storage data = RouterStorage.routerStorage();
131131

132132
EnumerableSet.Bytes32Set storage selectorsForPlugin = data.selectorsForPlugin[_pluginAddress];
133-
bytes4[] memory defaultSelectors = IPluginMap(functionMap).getAllFunctionsOfPlugin(_pluginAddress);
133+
bytes4[] memory defaultSelectors = IPluginMap(pluginMap).getAllFunctionsOfPlugin(_pluginAddress);
134134

135135
uint256 len = defaultSelectors.length;
136136
uint256 count = selectorsForPlugin.length() + defaultSelectors.length;
@@ -162,7 +162,7 @@ abstract contract Router is Multicall, ERC165, IRouter {
162162
RouterStorage.Data storage data = RouterStorage.routerStorage();
163163

164164
EnumerableSet.Bytes32Set storage overrideSelectors = data.allSelectors;
165-
Plugin[] memory defaultPlugins = IPluginMap(functionMap).getAllPlugins();
165+
Plugin[] memory defaultPlugins = IPluginMap(pluginMap).getAllPlugins();
166166

167167
uint256 overrideSelectorsLen = overrideSelectors.length();
168168
uint256 defaultPluginsLen = defaultPlugins.length;
@@ -211,7 +211,7 @@ abstract contract Router is Multicall, ERC165, IRouter {
211211
RouterStorage.Data storage data = RouterStorage.routerStorage();
212212

213213
// Revert: default plugin exists for function; use updatePlugin instead.
214-
try IPluginMap(functionMap).getPluginForFunction(_plugin.functionSelector) returns (address) {
214+
try IPluginMap(pluginMap).getPluginForFunction(_plugin.functionSelector) returns (address) {
215215
revert("Router: default plugin exists for function.");
216216
} catch {
217217
require(data.allSelectors.add(bytes32(_plugin.functionSelector)), "Router: plugin exists for function.");

contracts/extension/plugin/RouterImmutable.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ contract RouterImmutable is Router {
88
Constructor + initializer logic
99
//////////////////////////////////////////////////////////////*/
1010

11-
constructor(address _functionMap) Router(_functionMap) {}
11+
constructor(address _pluginMap) Router(_pluginMap) {}
1212

1313
/*///////////////////////////////////////////////////////////////
1414
Internal functions

contracts/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@thirdweb-dev/contracts",
33
"description": "Collection of smart contracts deployable via the thirdweb SDK, dashboard and CLI",
4-
"version": "3.2.10",
4+
"version": "3.3.0-5",
55
"license": "Apache-2.0",
66
"repository": {
77
"type": "git",
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
// ========== Internal imports ==========
5+
6+
import "../extension/PermissionsEnumerableLogic.sol";
7+
import "../extension/ERC2771ContextLogic.sol";
8+
import "../../extension/Multicall.sol";
9+
import "../../extension/plugin/Router.sol";
10+
11+
/**
12+
*
13+
* "Inherited by entrypoint" extensions.
14+
* - PermissionsEnumerable
15+
* - ERC2771Context
16+
* - Multicall
17+
*
18+
* "NOT inherited by entrypoint" extensions.
19+
* - TWMultichainRegistry
20+
*/
21+
22+
contract TWMultichainRegistryRouter is PermissionsEnumerableLogic, ERC2771ContextLogic, Router {
23+
/*///////////////////////////////////////////////////////////////
24+
Constructor + initializer logic
25+
//////////////////////////////////////////////////////////////*/
26+
27+
constructor(address _pluginMap, address[] memory _trustedForwarders)
28+
ERC2771ContextLogic(_trustedForwarders)
29+
Router(_pluginMap)
30+
{
31+
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
32+
}
33+
34+
/*///////////////////////////////////////////////////////////////
35+
Overridable Permissions
36+
//////////////////////////////////////////////////////////////*/
37+
38+
/// @dev Returns whether plug-in can be set in the given execution context.
39+
function _canSetPlugin() internal view override returns (bool) {
40+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
41+
}
42+
43+
function _msgSender() internal view override(ERC2771ContextLogic, PermissionsLogic) returns (address sender) {
44+
if (isTrustedForwarder(msg.sender)) {
45+
// The assembly code is more direct than the Solidity version using `abi.decode`.
46+
assembly {
47+
sender := shr(96, calldataload(sub(calldatasize(), 20)))
48+
}
49+
} else {
50+
return msg.sender;
51+
}
52+
}
53+
54+
function _msgData() internal view override(ERC2771ContextLogic, PermissionsLogic) returns (bytes calldata) {
55+
if (isTrustedForwarder(msg.sender)) {
56+
return msg.data[:msg.data.length - 20];
57+
} else {
58+
return msg.data;
59+
}
60+
}
61+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./ERC2771ContextLogic.sol";
5+
6+
interface IERC2771Context {
7+
function isTrustedForwarder(address forwarder) external view returns (bool);
8+
}
9+
10+
/**
11+
* @dev Context variant with ERC2771 support.
12+
*/
13+
abstract contract ERC2771ContextConsumer {
14+
function _msgSender() public view virtual returns (address sender) {
15+
if (IERC2771Context(address(this)).isTrustedForwarder(msg.sender)) {
16+
// The assembly code is more direct than the Solidity version using `abi.decode`.
17+
assembly {
18+
sender := shr(96, calldataload(sub(calldatasize(), 20)))
19+
}
20+
} else {
21+
return msg.sender;
22+
}
23+
}
24+
25+
function _msgData() public view virtual returns (bytes calldata) {
26+
if (IERC2771Context(address(this)).isTrustedForwarder(msg.sender)) {
27+
return msg.data[:msg.data.length - 20];
28+
} else {
29+
return msg.data;
30+
}
31+
}
32+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./ERC2771ContextStorage.sol";
5+
6+
/**
7+
* @dev Context variant with ERC2771 support.
8+
*/
9+
abstract contract ERC2771ContextLogic {
10+
constructor(address[] memory trustedForwarder) {
11+
ERC2771ContextStorage.Data storage data = ERC2771ContextStorage.erc2771ContextStorage();
12+
13+
for (uint256 i = 0; i < trustedForwarder.length; i++) {
14+
data._trustedForwarder[trustedForwarder[i]] = true;
15+
}
16+
}
17+
18+
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
19+
ERC2771ContextStorage.Data storage data = ERC2771ContextStorage.erc2771ContextStorage();
20+
return data._trustedForwarder[forwarder];
21+
}
22+
23+
function _msgSender() internal view virtual returns (address sender) {
24+
if (isTrustedForwarder(msg.sender)) {
25+
// The assembly code is more direct than the Solidity version using `abi.decode`.
26+
assembly {
27+
sender := shr(96, calldataload(sub(calldatasize(), 20)))
28+
}
29+
} else {
30+
return msg.sender;
31+
}
32+
}
33+
34+
function _msgData() internal view virtual returns (bytes calldata) {
35+
if (isTrustedForwarder(msg.sender)) {
36+
return msg.data[:msg.data.length - 20];
37+
} else {
38+
return msg.data;
39+
}
40+
}
41+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
library ERC2771ContextStorage {
5+
bytes32 public constant ERC2771_CONTEXT_STORAGE_POSITION = keccak256("erc2771.context.storage");
6+
7+
struct Data {
8+
mapping(address => bool) _trustedForwarder;
9+
}
10+
11+
function erc2771ContextStorage() internal pure returns (Data storage erc2771ContextData) {
12+
bytes32 position = ERC2771_CONTEXT_STORAGE_POSITION;
13+
assembly {
14+
erc2771ContextData.slot := position
15+
}
16+
}
17+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
interface IContext {
5+
function _msgSender() external view returns (address sender);
6+
7+
function _msgData() external view returns (bytes calldata);
8+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./PermissionsEnumerableStorage.sol";
5+
import "./PermissionsLogic.sol";
6+
7+
/**
8+
* @title PermissionsEnumerable
9+
* @dev This contracts provides extending-contracts with role-based access control mechanisms.
10+
* Also provides interfaces to view all members with a given role, and total count of members.
11+
*/
12+
contract PermissionsEnumerableLogic is IPermissionsEnumerable, PermissionsLogic {
13+
/**
14+
* @notice Returns the role-member from a list of members for a role,
15+
* at a given index.
16+
* @dev Returns `member` who has `role`, at `index` of role-members list.
17+
* See struct {RoleMembers}, and mapping {roleMembers}
18+
*
19+
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
20+
* @param index Index in list of current members for the role.
21+
*
22+
* @return member Address of account that has `role`
23+
*/
24+
function getRoleMember(bytes32 role, uint256 index) external view override returns (address member) {
25+
PermissionsEnumerableStorage.Data storage data = PermissionsEnumerableStorage.permissionsEnumerableStorage();
26+
uint256 currentIndex = data.roleMembers[role].index;
27+
uint256 check;
28+
29+
for (uint256 i = 0; i < currentIndex; i += 1) {
30+
if (data.roleMembers[role].members[i] != address(0)) {
31+
if (check == index) {
32+
member = data.roleMembers[role].members[i];
33+
return member;
34+
}
35+
check += 1;
36+
} else if (hasRole(role, address(0)) && i == data.roleMembers[role].indexOf[address(0)]) {
37+
check += 1;
38+
}
39+
}
40+
}
41+
42+
/**
43+
* @notice Returns total number of accounts that have a role.
44+
* @dev Returns `count` of accounts that have `role`.
45+
* See struct {RoleMembers}, and mapping {roleMembers}
46+
*
47+
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
48+
*
49+
* @return count Total number of accounts that have `role`
50+
*/
51+
function getRoleMemberCount(bytes32 role) external view override returns (uint256 count) {
52+
PermissionsEnumerableStorage.Data storage data = PermissionsEnumerableStorage.permissionsEnumerableStorage();
53+
uint256 currentIndex = data.roleMembers[role].index;
54+
55+
for (uint256 i = 0; i < currentIndex; i += 1) {
56+
if (data.roleMembers[role].members[i] != address(0)) {
57+
count += 1;
58+
}
59+
}
60+
if (hasRole(role, address(0))) {
61+
count += 1;
62+
}
63+
}
64+
65+
/// @dev Revokes `role` from `account`, and removes `account` from {roleMembers}
66+
/// See {_removeMember}
67+
function _revokeRole(bytes32 role, address account) internal override {
68+
super._revokeRole(role, account);
69+
_removeMember(role, account);
70+
}
71+
72+
/// @dev Grants `role` to `account`, and adds `account` to {roleMembers}
73+
/// See {_addMember}
74+
function _setupRole(bytes32 role, address account) internal override {
75+
super._setupRole(role, account);
76+
_addMember(role, account);
77+
}
78+
79+
/// @dev adds `account` to {roleMembers}, for `role`
80+
function _addMember(bytes32 role, address account) internal {
81+
PermissionsEnumerableStorage.Data storage data = PermissionsEnumerableStorage.permissionsEnumerableStorage();
82+
uint256 idx = data.roleMembers[role].index;
83+
data.roleMembers[role].index += 1;
84+
85+
data.roleMembers[role].members[idx] = account;
86+
data.roleMembers[role].indexOf[account] = idx;
87+
}
88+
89+
/// @dev removes `account` from {roleMembers}, for `role`
90+
function _removeMember(bytes32 role, address account) internal {
91+
PermissionsEnumerableStorage.Data storage data = PermissionsEnumerableStorage.permissionsEnumerableStorage();
92+
uint256 idx = data.roleMembers[role].indexOf[account];
93+
94+
delete data.roleMembers[role].members[idx];
95+
delete data.roleMembers[role].indexOf[account];
96+
}
97+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "../../extension/interface/IPermissionsEnumerable.sol";
5+
6+
library PermissionsEnumerableStorage {
7+
bytes32 public constant PERMISSIONS_ENUMERABLE_STORAGE_POSITION = keccak256("permissions.enumerable.storage");
8+
9+
/**
10+
* @notice A data structure to store data of members for a given role.
11+
*
12+
* @param index Current index in the list of accounts that have a role.
13+
* @param members map from index => address of account that has a role
14+
* @param indexOf map from address => index which the account has.
15+
*/
16+
struct RoleMembers {
17+
uint256 index;
18+
mapping(uint256 => address) members;
19+
mapping(address => uint256) indexOf;
20+
}
21+
22+
struct Data {
23+
/// @dev map from keccak256 hash of a role to its members' data. See {RoleMembers}.
24+
mapping(bytes32 => RoleMembers) roleMembers;
25+
}
26+
27+
function permissionsEnumerableStorage() internal pure returns (Data storage permissionsEnumerableData) {
28+
bytes32 position = PERMISSIONS_ENUMERABLE_STORAGE_POSITION;
29+
assembly {
30+
permissionsEnumerableData.slot := position
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)