Skip to content

Commit

Permalink
MultichainRegistry: new code pattern (#285)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kumaryash90 authored Jan 4, 2023
1 parent 3f61125 commit 365bf69
Show file tree
Hide file tree
Showing 29 changed files with 2,230 additions and 44 deletions.
16 changes: 8 additions & 8 deletions contracts/extension/plugin/Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ abstract contract Router is Multicall, ERC165, IRouter {
State variables
//////////////////////////////////////////////////////////////*/

address public immutable functionMap;
address public immutable pluginMap;

/*///////////////////////////////////////////////////////////////
Constructor + initializer logic
//////////////////////////////////////////////////////////////*/

constructor(address _functionMap) {
functionMap = _functionMap;
constructor(address _pluginMap) {
pluginMap = _pluginMap;
}

/*///////////////////////////////////////////////////////////////
Expand All @@ -57,7 +57,7 @@ abstract contract Router is Multicall, ERC165, IRouter {
fallback() external payable virtual {
address _pluginAddress = _getPluginForFunction(msg.sig);
if (_pluginAddress == address(0)) {
_pluginAddress = IPluginMap(functionMap).getPluginForFunction(msg.sig);
_pluginAddress = IPluginMap(pluginMap).getPluginForFunction(msg.sig);
}
_delegate(_pluginAddress);
}
Expand Down Expand Up @@ -122,15 +122,15 @@ abstract contract Router is Multicall, ERC165, IRouter {
function getPluginForFunction(bytes4 _selector) public view returns (address) {
address pluginAddress = _getPluginForFunction(_selector);

return pluginAddress != address(0) ? pluginAddress : IPluginMap(functionMap).getPluginForFunction(_selector);
return pluginAddress != address(0) ? pluginAddress : IPluginMap(pluginMap).getPluginForFunction(_selector);
}

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

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

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

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

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

// Revert: default plugin exists for function; use updatePlugin instead.
try IPluginMap(functionMap).getPluginForFunction(_plugin.functionSelector) returns (address) {
try IPluginMap(pluginMap).getPluginForFunction(_plugin.functionSelector) returns (address) {
revert("Router: default plugin exists for function.");
} catch {
require(data.allSelectors.add(bytes32(_plugin.functionSelector)), "Router: plugin exists for function.");
Expand Down
2 changes: 1 addition & 1 deletion contracts/extension/plugin/RouterImmutable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ contract RouterImmutable is Router {
Constructor + initializer logic
//////////////////////////////////////////////////////////////*/

constructor(address _functionMap) Router(_functionMap) {}
constructor(address _pluginMap) Router(_pluginMap) {}

/*///////////////////////////////////////////////////////////////
Internal functions
Expand Down
2 changes: 1 addition & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@thirdweb-dev/contracts",
"description": "Collection of smart contracts deployable via the thirdweb SDK, dashboard and CLI",
"version": "3.2.10",
"version": "3.3.0-5",
"license": "Apache-2.0",
"repository": {
"type": "git",
Expand Down
61 changes: 61 additions & 0 deletions contracts/registry/entrypoint/TWMultichainRegistryRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

// ========== Internal imports ==========

import "../extension/PermissionsEnumerableLogic.sol";
import "../extension/ERC2771ContextLogic.sol";
import "../../extension/Multicall.sol";
import "../../extension/plugin/Router.sol";

/**
*
* "Inherited by entrypoint" extensions.
* - PermissionsEnumerable
* - ERC2771Context
* - Multicall
*
* "NOT inherited by entrypoint" extensions.
* - TWMultichainRegistry
*/

contract TWMultichainRegistryRouter is PermissionsEnumerableLogic, ERC2771ContextLogic, Router {
/*///////////////////////////////////////////////////////////////
Constructor + initializer logic
//////////////////////////////////////////////////////////////*/

constructor(address _pluginMap, address[] memory _trustedForwarders)
ERC2771ContextLogic(_trustedForwarders)
Router(_pluginMap)
{
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/*///////////////////////////////////////////////////////////////
Overridable Permissions
//////////////////////////////////////////////////////////////*/

/// @dev Returns whether plug-in can be set in the given execution context.
function _canSetPlugin() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

function _msgSender() internal view override(ERC2771ContextLogic, PermissionsLogic) returns (address sender) {
if (isTrustedForwarder(msg.sender)) {
// The assembly code is more direct than the Solidity version using `abi.decode`.
assembly {
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
return msg.sender;
}
}

function _msgData() internal view override(ERC2771ContextLogic, PermissionsLogic) returns (bytes calldata) {
if (isTrustedForwarder(msg.sender)) {
return msg.data[:msg.data.length - 20];
} else {
return msg.data;
}
}
}
32 changes: 32 additions & 0 deletions contracts/registry/extension/ERC2771ContextConsumer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./ERC2771ContextLogic.sol";

interface IERC2771Context {
function isTrustedForwarder(address forwarder) external view returns (bool);
}

/**
* @dev Context variant with ERC2771 support.
*/
abstract contract ERC2771ContextConsumer {
function _msgSender() public view virtual returns (address sender) {
if (IERC2771Context(address(this)).isTrustedForwarder(msg.sender)) {
// The assembly code is more direct than the Solidity version using `abi.decode`.
assembly {
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
return msg.sender;
}
}

function _msgData() public view virtual returns (bytes calldata) {
if (IERC2771Context(address(this)).isTrustedForwarder(msg.sender)) {
return msg.data[:msg.data.length - 20];
} else {
return msg.data;
}
}
}
41 changes: 41 additions & 0 deletions contracts/registry/extension/ERC2771ContextLogic.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./ERC2771ContextStorage.sol";

/**
* @dev Context variant with ERC2771 support.
*/
abstract contract ERC2771ContextLogic {
constructor(address[] memory trustedForwarder) {
ERC2771ContextStorage.Data storage data = ERC2771ContextStorage.erc2771ContextStorage();

for (uint256 i = 0; i < trustedForwarder.length; i++) {
data._trustedForwarder[trustedForwarder[i]] = true;
}
}

function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
ERC2771ContextStorage.Data storage data = ERC2771ContextStorage.erc2771ContextStorage();
return data._trustedForwarder[forwarder];
}

function _msgSender() internal view virtual returns (address sender) {
if (isTrustedForwarder(msg.sender)) {
// The assembly code is more direct than the Solidity version using `abi.decode`.
assembly {
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
return msg.sender;
}
}

function _msgData() internal view virtual returns (bytes calldata) {
if (isTrustedForwarder(msg.sender)) {
return msg.data[:msg.data.length - 20];
} else {
return msg.data;
}
}
}
17 changes: 17 additions & 0 deletions contracts/registry/extension/ERC2771ContextStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

library ERC2771ContextStorage {
bytes32 public constant ERC2771_CONTEXT_STORAGE_POSITION = keccak256("erc2771.context.storage");

struct Data {
mapping(address => bool) _trustedForwarder;
}

function erc2771ContextStorage() internal pure returns (Data storage erc2771ContextData) {
bytes32 position = ERC2771_CONTEXT_STORAGE_POSITION;
assembly {
erc2771ContextData.slot := position
}
}
}
8 changes: 8 additions & 0 deletions contracts/registry/extension/IContext.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

interface IContext {
function _msgSender() external view returns (address sender);

function _msgData() external view returns (bytes calldata);
}
97 changes: 97 additions & 0 deletions contracts/registry/extension/PermissionsEnumerableLogic.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./PermissionsEnumerableStorage.sol";
import "./PermissionsLogic.sol";

/**
* @title PermissionsEnumerable
* @dev This contracts provides extending-contracts with role-based access control mechanisms.
* Also provides interfaces to view all members with a given role, and total count of members.
*/
contract PermissionsEnumerableLogic is IPermissionsEnumerable, PermissionsLogic {
/**
* @notice Returns the role-member from a list of members for a role,
* at a given index.
* @dev Returns `member` who has `role`, at `index` of role-members list.
* See struct {RoleMembers}, and mapping {roleMembers}
*
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
* @param index Index in list of current members for the role.
*
* @return member Address of account that has `role`
*/
function getRoleMember(bytes32 role, uint256 index) external view override returns (address member) {
PermissionsEnumerableStorage.Data storage data = PermissionsEnumerableStorage.permissionsEnumerableStorage();
uint256 currentIndex = data.roleMembers[role].index;
uint256 check;

for (uint256 i = 0; i < currentIndex; i += 1) {
if (data.roleMembers[role].members[i] != address(0)) {
if (check == index) {
member = data.roleMembers[role].members[i];
return member;
}
check += 1;
} else if (hasRole(role, address(0)) && i == data.roleMembers[role].indexOf[address(0)]) {
check += 1;
}
}
}

/**
* @notice Returns total number of accounts that have a role.
* @dev Returns `count` of accounts that have `role`.
* See struct {RoleMembers}, and mapping {roleMembers}
*
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
*
* @return count Total number of accounts that have `role`
*/
function getRoleMemberCount(bytes32 role) external view override returns (uint256 count) {
PermissionsEnumerableStorage.Data storage data = PermissionsEnumerableStorage.permissionsEnumerableStorage();
uint256 currentIndex = data.roleMembers[role].index;

for (uint256 i = 0; i < currentIndex; i += 1) {
if (data.roleMembers[role].members[i] != address(0)) {
count += 1;
}
}
if (hasRole(role, address(0))) {
count += 1;
}
}

/// @dev Revokes `role` from `account`, and removes `account` from {roleMembers}
/// See {_removeMember}
function _revokeRole(bytes32 role, address account) internal override {
super._revokeRole(role, account);
_removeMember(role, account);
}

/// @dev Grants `role` to `account`, and adds `account` to {roleMembers}
/// See {_addMember}
function _setupRole(bytes32 role, address account) internal override {
super._setupRole(role, account);
_addMember(role, account);
}

/// @dev adds `account` to {roleMembers}, for `role`
function _addMember(bytes32 role, address account) internal {
PermissionsEnumerableStorage.Data storage data = PermissionsEnumerableStorage.permissionsEnumerableStorage();
uint256 idx = data.roleMembers[role].index;
data.roleMembers[role].index += 1;

data.roleMembers[role].members[idx] = account;
data.roleMembers[role].indexOf[account] = idx;
}

/// @dev removes `account` from {roleMembers}, for `role`
function _removeMember(bytes32 role, address account) internal {
PermissionsEnumerableStorage.Data storage data = PermissionsEnumerableStorage.permissionsEnumerableStorage();
uint256 idx = data.roleMembers[role].indexOf[account];

delete data.roleMembers[role].members[idx];
delete data.roleMembers[role].indexOf[account];
}
}
33 changes: 33 additions & 0 deletions contracts/registry/extension/PermissionsEnumerableStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "../../extension/interface/IPermissionsEnumerable.sol";

library PermissionsEnumerableStorage {
bytes32 public constant PERMISSIONS_ENUMERABLE_STORAGE_POSITION = keccak256("permissions.enumerable.storage");

/**
* @notice A data structure to store data of members for a given role.
*
* @param index Current index in the list of accounts that have a role.
* @param members map from index => address of account that has a role
* @param indexOf map from address => index which the account has.
*/
struct RoleMembers {
uint256 index;
mapping(uint256 => address) members;
mapping(address => uint256) indexOf;
}

struct Data {
/// @dev map from keccak256 hash of a role to its members' data. See {RoleMembers}.
mapping(bytes32 => RoleMembers) roleMembers;
}

function permissionsEnumerableStorage() internal pure returns (Data storage permissionsEnumerableData) {
bytes32 position = PERMISSIONS_ENUMERABLE_STORAGE_POSITION;
assembly {
permissionsEnumerableData.slot := position
}
}
}
Loading

0 comments on commit 365bf69

Please sign in to comment.