|
| 1 | +// SPDX-License-Identifier: AGPL-3.0-only |
| 2 | +pragma solidity ^0.8.0; |
| 3 | + |
| 4 | +import "@uma/core/contracts/common/implementation/MultiCaller.sol"; |
| 5 | +import "@openzeppelin/contracts/access/AccessControl.sol"; |
| 6 | + |
| 7 | +contract PermissionSplitterProxy is AccessControl, MultiCaller { |
| 8 | + // Inherited admin role from AccessControl. Should be assigned to Across DAO Safe. |
| 9 | + // bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; |
| 10 | + |
| 11 | + // Maps function signatures to role identifiers, which gatekeeps access to these functions to |
| 12 | + // only role holders. |
| 13 | + mapping(bytes4 => bytes32) public roleForSelector; |
| 14 | + |
| 15 | + address public target; |
| 16 | + |
| 17 | + event TargetUpdated(address indexed newTarget); |
| 18 | + event RoleForSelectorSet(bytes4 indexed selector, bytes32 indexed role); |
| 19 | + |
| 20 | + constructor(address _target) { |
| 21 | + _init(_target); |
| 22 | + } |
| 23 | + |
| 24 | + // Public function! |
| 25 | + // Note: these have two underscores in front to prevent any collisions with the target contract. |
| 26 | + function __setTarget(address _target) public onlyRole(DEFAULT_ADMIN_ROLE) { |
| 27 | + target = _target; |
| 28 | + emit TargetUpdated(_target); |
| 29 | + } |
| 30 | + |
| 31 | + // Public function! |
| 32 | + // Note: these have two underscores in front to prevent any collisions with the target contract. |
| 33 | + function __setRoleForSelector(bytes4 selector, bytes32 role) public onlyRole(DEFAULT_ADMIN_ROLE) { |
| 34 | + roleForSelector[selector] = role; |
| 35 | + emit RoleForSelectorSet(selector, role); |
| 36 | + } |
| 37 | + |
| 38 | + function _init(address _target) internal virtual { |
| 39 | + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); |
| 40 | + __setTarget(_target); |
| 41 | + } |
| 42 | + |
| 43 | + function _isAllowedToCall(address caller, bytes calldata callData) internal view virtual returns (bool) { |
| 44 | + bytes4 selector; |
| 45 | + if (callData.length < 4) { |
| 46 | + // This handles any empty callData, which is a call to the fallback function. |
| 47 | + selector = bytes4(0); |
| 48 | + } else { |
| 49 | + selector = bytes4(callData[:4]); |
| 50 | + } |
| 51 | + return hasRole(DEFAULT_ADMIN_ROLE, caller) || hasRole(roleForSelector[selector], caller); |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * @dev Forwards the current call to `implementation`. |
| 56 | + * |
| 57 | + * This function does not return to its internal call site, it will return directly to the external caller. |
| 58 | + * Note: this function is a modified _delegate function here: |
| 59 | + // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/002a7c8812e73c282b91e14541ce9b93a6de1172/contracts/proxy/Proxy.sol#L22-L45 |
| 60 | + */ |
| 61 | + function _forward(address _target) internal { |
| 62 | + assembly { |
| 63 | + // Copy msg.data. We take full control of memory in this inline assembly |
| 64 | + // block because it will not return to Solidity code. We overwrite the |
| 65 | + // Solidity scratch pad at memory position 0. |
| 66 | + calldatacopy(0, 0, calldatasize()) |
| 67 | + |
| 68 | + // Call the implementation. |
| 69 | + // out and outsize are 0 because we don't know the size yet. |
| 70 | + let result := call(gas(), _target, callvalue(), 0, calldatasize(), 0, 0) |
| 71 | + |
| 72 | + // Copy the returned data. |
| 73 | + returndatacopy(0, 0, returndatasize()) |
| 74 | + |
| 75 | + switch result |
| 76 | + // call returns 0 on error. |
| 77 | + case 0 { |
| 78 | + revert(0, returndatasize()) |
| 79 | + } |
| 80 | + default { |
| 81 | + return(0, returndatasize()) |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + // Executes an action on the target. |
| 87 | + function _executeAction() internal virtual { |
| 88 | + require(_isAllowedToCall(msg.sender, msg.data), "Not allowed to call"); |
| 89 | + _forward(target); |
| 90 | + } |
| 91 | + |
| 92 | + /** |
| 93 | + * @dev Fallback function that forwards calls to the target. Will run if no other |
| 94 | + * function in the contract matches the call data. |
| 95 | + */ |
| 96 | + fallback() external payable virtual { |
| 97 | + _executeAction(); |
| 98 | + } |
| 99 | + |
| 100 | + /** |
| 101 | + * @dev Fallback function that delegates calls to the target. Will run if call data |
| 102 | + * is empty. |
| 103 | + */ |
| 104 | + receive() external payable virtual { |
| 105 | + _executeAction(); |
| 106 | + } |
| 107 | +} |
0 commit comments