Skip to content

Commit d17fa59

Browse files
nicholaspaimrice32
andauthored
Create PermissionSplitter (#300)
* Create PermissionSplitter.sol * add permission splitter examples Signed-off-by: nicholaspai <npai.nyc@gmail.com> * Update PermissionSplitter.sol * Update PermissionSplitter.sol * WIP Signed-off-by: Matt Rice <matthewcrice32@gmail.com> * WIP Signed-off-by: Matt Rice <matthewcrice32@gmail.com> * Add tests Signed-off-by: Matt Rice <matthewcrice32@gmail.com> * WIP Signed-off-by: Matt Rice <matthewcrice32@gmail.com> * WIP Signed-off-by: Matt Rice <matthewcrice32@gmail.com> * add events Signed-off-by: Matt Rice <matthewcrice32@gmail.com> --------- Signed-off-by: nicholaspai <npai.nyc@gmail.com> Signed-off-by: Matt Rice <matthewcrice32@gmail.com> Co-authored-by: Matt Rice <matthewcrice32@gmail.com>
1 parent 41b2b09 commit d17fa59

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
}

test/PermissionSplitterProxy.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { getContractFactory, SignerWithAddress, expect, Contract, ethers } from "../utils/utils";
2+
import { hubPoolFixture } from "./fixtures/HubPool.Fixture";
3+
4+
let hubPool: Contract, weth: Contract, usdc: Contract, permissionSplitter: Contract, hubPoolProxy: Contract;
5+
let owner: SignerWithAddress, delegate: SignerWithAddress;
6+
let delegateRole: string, defaultAdminRole: string;
7+
8+
const enableTokenSelector = "0xb60c2d7d";
9+
10+
describe("PermissionSplitterProxy", function () {
11+
beforeEach(async function () {
12+
[owner, delegate] = await ethers.getSigners();
13+
({ weth, hubPool, usdc } = await hubPoolFixture());
14+
const permissionSplitterFactory = await getContractFactory("PermissionSplitterProxy", owner);
15+
const hubPoolFactory = await getContractFactory("HubPool", owner);
16+
17+
permissionSplitter = await permissionSplitterFactory.deploy(hubPool.address);
18+
hubPoolProxy = hubPoolFactory.attach(permissionSplitter.address);
19+
delegateRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("DELEGATE_ROLE"));
20+
permissionSplitter.connect(owner).grantRole(delegateRole, delegate.address);
21+
defaultAdminRole = ethers.utils.hexZeroPad("0x00", 32);
22+
await hubPool.transferOwnership(permissionSplitter.address);
23+
});
24+
25+
it("Cannot run method until whitelisted", async function () {
26+
await expect(hubPoolProxy.connect(delegate).enableL1TokenForLiquidityProvision(weth.address)).to.be.reverted;
27+
await permissionSplitter.connect(owner).__setRoleForSelector(enableTokenSelector, delegateRole);
28+
await hubPoolProxy.connect(delegate).enableL1TokenForLiquidityProvision(weth.address);
29+
expect((await hubPool.callStatic.pooledTokens(weth.address)).isEnabled).to.equal(true);
30+
});
31+
it("Owner can run without whitelisting", async function () {
32+
await hubPoolProxy.connect(owner).enableL1TokenForLiquidityProvision(weth.address);
33+
expect((await hubPool.callStatic.pooledTokens(weth.address)).isEnabled).to.equal(true);
34+
});
35+
36+
it("Owner can revoke role", async function () {
37+
await expect(hubPoolProxy.connect(delegate).enableL1TokenForLiquidityProvision(weth.address)).to.be.reverted;
38+
await permissionSplitter.connect(owner).__setRoleForSelector(enableTokenSelector, delegateRole);
39+
await hubPoolProxy.connect(delegate).enableL1TokenForLiquidityProvision(weth.address);
40+
expect((await hubPool.callStatic.pooledTokens(weth.address)).isEnabled).to.equal(true);
41+
42+
await permissionSplitter.connect(owner).revokeRole(delegateRole, delegate.address);
43+
await expect(hubPoolProxy.connect(delegate).enableL1TokenForLiquidityProvision(usdc.address)).to.be.reverted;
44+
});
45+
46+
it("Owner can revoke selector", async function () {
47+
await expect(hubPoolProxy.connect(delegate).enableL1TokenForLiquidityProvision(weth.address)).to.be.reverted;
48+
await permissionSplitter.connect(owner).__setRoleForSelector(enableTokenSelector, delegateRole);
49+
await hubPoolProxy.connect(delegate).enableL1TokenForLiquidityProvision(weth.address);
50+
expect((await hubPool.callStatic.pooledTokens(weth.address)).isEnabled).to.equal(true);
51+
52+
await permissionSplitter.connect(owner).__setRoleForSelector(enableTokenSelector, defaultAdminRole);
53+
await expect(hubPoolProxy.connect(delegate).enableL1TokenForLiquidityProvision(usdc.address)).to.be.reverted;
54+
});
55+
});

0 commit comments

Comments
 (0)