@lazy-sol/access-control 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +21 -0
- package/.solcover.js +39 -0
- package/CONTRIBUTING.md +200 -0
- package/CRIBBED_CODE.txt +26 -0
- package/LICENSE.txt +22 -0
- package/README.md +162 -0
- package/artifacts/contracts/OwnableToAccessControlAdapter.sol/OwnableToAccessControlAdapter.json +342 -0
- package/contracts/AccessControl.sol +305 -0
- package/contracts/AdapterFactory.sol +56 -0
- package/contracts/OwnableToAccessControlAdapter.sol +231 -0
- package/contracts/mocks/AccessControlMock.sol +14 -0
- package/contracts/mocks/TetherToken.sol +443 -0
- package/deploy/deploy-AdapterFactory.js +59 -0
- package/deployments/goerli/.chainId +1 -0
- package/deployments/goerli/AdapterFactory.json +104 -0
- package/deployments/goerli/solcInputs/9f8f20c7b4fd0796d45c56d37e790191.json +42 -0
- package/docs/commit_policy.md +201 -0
- package/docs/pull_request_template.md +40 -0
- package/docs/style_guides.md +240 -0
- package/hardhat.config.js +359 -0
- package/package.json +39 -0
- package/test/adapter_factory.js +54 -0
- package/test/include/deployment_routines.js +150 -0
- package/test/include/features_roles.js +42 -0
- package/test/include/rbac.behaviour.js +315 -0
- package/test/ownable_to_rbac_adapter.js +100 -0
- package/test/ownable_to_rbac_adapter_rbac.js +57 -0
- package/test/rbac_core.js +24 -0
- package/test/rbac_modifier.js +53 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
pragma solidity >=0.6.2;
|
3
|
+
|
4
|
+
import "./OwnableToAccessControlAdapter.sol";
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @notice Ownable is a contract which is aware of its owner, that is has owner() function
|
8
|
+
*/
|
9
|
+
interface Ownable {
|
10
|
+
/**
|
11
|
+
* @notice Smart contract owner
|
12
|
+
*
|
13
|
+
* @return the address of the smart contract owner
|
14
|
+
*/
|
15
|
+
function owner() external returns(address);
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* @title Adapter Factory
|
20
|
+
*
|
21
|
+
* @notice Helper contract simplifying the deployment of the OwnableToAccessControlAdapter
|
22
|
+
*
|
23
|
+
* @author Basil Gorin
|
24
|
+
*/
|
25
|
+
contract AdapterFactory {
|
26
|
+
/**
|
27
|
+
* @dev Fired in deployNewOwnableToAccessControlAdapter
|
28
|
+
*
|
29
|
+
* @param adapterAddress newly deployed OwnableToAccessControlAdapter address
|
30
|
+
* @param ownableTargetAddress OZ Ownable target contract address
|
31
|
+
*/
|
32
|
+
event NewOwnableToAccessControlAdapterDeployed(address indexed adapterAddress, address indexed ownableTargetAddress);
|
33
|
+
|
34
|
+
/**
|
35
|
+
* @notice Deploys new OwnableToAccessControlAdapter bound to the OZ Ownable contract specified.
|
36
|
+
* Can be executed only by the OZ Ownable target owner. This owner is expected to transfer
|
37
|
+
* the ownership to the newly deployed OwnableToAccessControlAdapter contract address.
|
38
|
+
*
|
39
|
+
* @param targetAddress OZ Ownable target address to bind OwnableToAccessControlAdapter to
|
40
|
+
* @return address of the newly deployed OwnableToAccessControlAdapter contract
|
41
|
+
*/
|
42
|
+
function deployNewOwnableToAccessControlAdapter(address targetAddress) public returns(address) {
|
43
|
+
// verify sender is a target owner
|
44
|
+
require(Ownable(targetAddress).owner() == msg.sender, "not an owner");
|
45
|
+
|
46
|
+
// deploy the OwnableToAccessControlAdapter
|
47
|
+
// and set its ownership immediately to the tx executor (msg.sender)
|
48
|
+
address adapterAddress = address(new OwnableToAccessControlAdapter(targetAddress, msg.sender));
|
49
|
+
|
50
|
+
// emit an event
|
51
|
+
emit NewOwnableToAccessControlAdapterDeployed(adapterAddress, targetAddress);
|
52
|
+
|
53
|
+
// return address of the newly deployed OwnableToAccessControlAdapter contract
|
54
|
+
return adapterAddress;
|
55
|
+
}
|
56
|
+
}
|
@@ -0,0 +1,231 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
pragma solidity >=0.6.2; // breaking changes in .call() (0.5.0), allow .call{}() (0.6.2)
|
3
|
+
|
4
|
+
import "./AccessControl.sol";
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @title OZ Ownable to AccessControl Adapter (short: AccessControl Adapter)
|
8
|
+
*
|
9
|
+
* @notice Helper contract allowing to change the access model of the already deployed
|
10
|
+
* OpenZeppelin Ownable contract to the AccessControl model
|
11
|
+
*
|
12
|
+
* @dev Installation Flow
|
13
|
+
* Prerequisite: deployed OZ Ownable contract (target contract) address (target_address)
|
14
|
+
*
|
15
|
+
* 1. Deploy the AccessControl Adapter bound to the already deployed OZ Ownable contract
|
16
|
+
* (specify the target OZ Ownable contract address in the constructor upon the deployment)
|
17
|
+
*
|
18
|
+
* const adapter = await (artifacts.require("OwnableToAccessControlAdapter")).new(target_address);
|
19
|
+
*
|
20
|
+
* 2. Define what Ownable-restricted public functions on the target contract you'd like to be able
|
21
|
+
* to provide access to through the adapter contract
|
22
|
+
*
|
23
|
+
* 3. Map every such function with the role required to execute it using `updateAccessRole()` function
|
24
|
+
* For example, to be able to provide an access to the transferOwnership(address) function, you could do
|
25
|
+
*
|
26
|
+
* const ROLE_TRANSFER_OWNERSHIP_MANAGER = 0x00010000;
|
27
|
+
* await adapter.updateAccessRole("transferOwnership(address)", ROLE_TRANSFER_OWNERSHIP_MANAGER);
|
28
|
+
*
|
29
|
+
* 4. Provide the roles to the corresponding operators as you would usually do with AccessControl
|
30
|
+
* For example, if you wish an address 0x00000000000000000000000000000000000Ff1CE to grant an access to the
|
31
|
+
* transferOwnership(address) function on the target, you could do
|
32
|
+
*
|
33
|
+
* const operator = "0x00000000000000000000000000000000000Ff1CE";
|
34
|
+
* await adapter.updateRole(operator, ROLE_TRANSFER_OWNERSHIP_MANAGER);
|
35
|
+
*
|
36
|
+
* 5. Transfer the ownership of the target contract to the deployed AccessControl Adapter contract
|
37
|
+
* Note that you can also do steps 2-4 after the step 5
|
38
|
+
*
|
39
|
+
* @dev Usage Flow
|
40
|
+
* Prerequisite: installed AccessControl Adapter with the access to at least one restricted target contract
|
41
|
+
* function configured
|
42
|
+
*
|
43
|
+
* To execute the restricted access function on the target contract via the AccessControl Adapter
|
44
|
+
* 1. Use target contract ABI to construct a low-level function call calldata
|
45
|
+
* For example, to construct the transferOwnership() function calldata to transfer the ownership to the
|
46
|
+
* 0x00000000000000000000000000000000DEAdc0De address, you could do
|
47
|
+
*
|
48
|
+
* const to = "0x00000000000000000000000000000000DEAdc0De";
|
49
|
+
* const calldata = target.contract.methods.transferOwnership(to).encodeABI();
|
50
|
+
*
|
51
|
+
* 2. Execute a low-level function call on the AccessControl Adapter contract using the constructed calldata
|
52
|
+
* For example, to execute the transferOwnership() function (prepared in step 1), you could do
|
53
|
+
*
|
54
|
+
* await web3.eth.sendTransaction({
|
55
|
+
* from: operator,
|
56
|
+
* to: adapter.address,
|
57
|
+
* data: calldata,
|
58
|
+
* }
|
59
|
+
*
|
60
|
+
* 3. It is also ok to add an ether to the transaction by adding a value field to the `sendTransaction` call,
|
61
|
+
* as well as sending plain ether transfer transaction, as long as target contract has payable functions,
|
62
|
+
* and/or has a default payable receiver
|
63
|
+
*
|
64
|
+
* @author Basil Gorin
|
65
|
+
*/
|
66
|
+
contract OwnableToAccessControlAdapter is AccessControl {
|
67
|
+
/**
|
68
|
+
* @dev Target OZ Ownable contract AccessControl Adapter executes the transactions on
|
69
|
+
*
|
70
|
+
* @dev Target contract must transfer its ownership to the AccessControl Adapter
|
71
|
+
*/
|
72
|
+
address public target;
|
73
|
+
|
74
|
+
/**
|
75
|
+
* @dev Access roles mapping stores the roles required to access the functions on the
|
76
|
+
* target contract, guarding it from the unauthorized access
|
77
|
+
*
|
78
|
+
* @dev Maps function selector (bytes4) on the target contract to the access role (permission)
|
79
|
+
* required to execute the function
|
80
|
+
*/
|
81
|
+
mapping(bytes4 => uint256) public accessRoles;
|
82
|
+
|
83
|
+
/**
|
84
|
+
* @notice Access Roles manager is responsible for assigning the access roles to functions
|
85
|
+
*
|
86
|
+
* @dev Role ROLE_ACCESS_MANAGER allows modifying `accessRoles` mapping
|
87
|
+
*/
|
88
|
+
uint256 public constant ROLE_ACCESS_ROLES_MANAGER = 0x2000000000000000000000000000000000000000000000000000000000000000;
|
89
|
+
|
90
|
+
/**
|
91
|
+
* @dev Fired in `updateAccessRole` when the `accessRoles` mapping is updated
|
92
|
+
*
|
93
|
+
* @param selector selector of the function which corresponding access role was updated
|
94
|
+
* @param role effective required role to execute the function defined by the selector
|
95
|
+
*/
|
96
|
+
event AccessRoleUpdated(bytes4 selector, uint256 role);
|
97
|
+
|
98
|
+
/**
|
99
|
+
* @dev Logs function execution result on the target if the execution completed successfully
|
100
|
+
*
|
101
|
+
* @param selector selector of the function which was executed on the target contract
|
102
|
+
* @param data full calldata payload passed to the target contract (includes the 4-bytes selector)
|
103
|
+
* @param result execution response from the target contract
|
104
|
+
*/
|
105
|
+
event ExecutionComplete(bytes4 selector, bytes data, bytes result);
|
106
|
+
|
107
|
+
/**
|
108
|
+
* @dev Deploys an AccessControl Adapter binding it to the target OZ Ownable contract,
|
109
|
+
* and setting the ownership of the adapter itself to the deployer
|
110
|
+
*
|
111
|
+
* @param _target target OZ Ownable contract address
|
112
|
+
* @param _owner smart contract owner having full privileges
|
113
|
+
*/
|
114
|
+
constructor(address _target, address _owner) public AccessControl(_owner, 0) {
|
115
|
+
// verify the inputs
|
116
|
+
require(_target != address(0), "zero address");
|
117
|
+
|
118
|
+
// initialize internal contract state
|
119
|
+
target = _target;
|
120
|
+
}
|
121
|
+
|
122
|
+
/**
|
123
|
+
* @dev Updates the access role required to execute the function defined by its signature
|
124
|
+
* on the target contract
|
125
|
+
*
|
126
|
+
* @dev More on function signatures and selectors: https://docs.soliditylang.org/en/develop/abi-spec.html
|
127
|
+
*
|
128
|
+
* @param signature function signature on the target contract, for example
|
129
|
+
* "transferOwnership(address)"
|
130
|
+
* @param role role required to execute this function, or zero to disable
|
131
|
+
* access to the specified function for everyone
|
132
|
+
*/
|
133
|
+
function updateAccessRole(string memory signature, uint256 role) public {
|
134
|
+
// delegate to `updateAccessRole(bytes4, uint256)`
|
135
|
+
updateAccessRole(bytes4(keccak256(bytes(signature))), role);
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
* @dev Updates the access role required to execute the function defined by its selector
|
140
|
+
* on the target contract
|
141
|
+
*
|
142
|
+
* @dev More on function signatures and selectors: https://docs.soliditylang.org/en/develop/abi-spec.html
|
143
|
+
*
|
144
|
+
* @param selector function selector on the target contract, for example
|
145
|
+
* 0xf2fde38b selector corresponds to the "transferOwnership(address)" function
|
146
|
+
* @param role role required to execute this function, or zero to disable
|
147
|
+
* access to the specified function for everyone
|
148
|
+
*/
|
149
|
+
function updateAccessRole(bytes4 selector, uint256 role) public {
|
150
|
+
// verify the access permission
|
151
|
+
require(isSenderInRole(ROLE_ACCESS_ROLES_MANAGER), "access denied");
|
152
|
+
|
153
|
+
// update the function access role
|
154
|
+
accessRoles[selector] = role;
|
155
|
+
|
156
|
+
// emit an event
|
157
|
+
emit AccessRoleUpdated(selector, role);
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* @dev Low-level execute of the data calldata on the target contract
|
162
|
+
*
|
163
|
+
* @dev This function extracts the target function selector from the calldata specified
|
164
|
+
* and verifies transaction executor permission to access the function on the target
|
165
|
+
* using the `accessRoles` mapping
|
166
|
+
*
|
167
|
+
* @dev Throws if there is no `accessRoles` mapping configured for the function
|
168
|
+
* @dev Throws if transaction executor role doesn't contain the required role from `accessRoles` mapping
|
169
|
+
* @dev Throws if execution on the target returns an error
|
170
|
+
*
|
171
|
+
* @param data low-level calldata to be passed as is to the target contract for the execution
|
172
|
+
* @return the response from the target contract after the successful execution
|
173
|
+
*/
|
174
|
+
function execute(bytes memory data) public payable returns(bytes memory) {
|
175
|
+
// extract the selector (first 4 bytes as bytes4) using assembly
|
176
|
+
bytes4 selector;
|
177
|
+
assembly {
|
178
|
+
// load the first word after the length field
|
179
|
+
selector := mload(add(data, 32))
|
180
|
+
}
|
181
|
+
|
182
|
+
// zero data length means we're trying to execute the receive() function on
|
183
|
+
// the target and supply some ether to the target; in this case we don't need a security check
|
184
|
+
// if the data is present, we're executing some real function and must do a security check
|
185
|
+
if(data.length != 0) {
|
186
|
+
// determine the role required to access the function
|
187
|
+
uint256 roleRequired = accessRoles[selector];
|
188
|
+
|
189
|
+
// verify function access role was already set
|
190
|
+
require(roleRequired != 0, "access role not set");
|
191
|
+
|
192
|
+
// verify the access permission
|
193
|
+
require(isSenderInRole(roleRequired), "access denied");
|
194
|
+
}
|
195
|
+
|
196
|
+
// execute the call on the target
|
197
|
+
(bool success, bytes memory result) = address(target).call{value: msg.value}(data);
|
198
|
+
|
199
|
+
// verify the execution completed successfully
|
200
|
+
require(success, "execution failed");
|
201
|
+
|
202
|
+
// emit an event
|
203
|
+
emit ExecutionComplete(selector, data, result);
|
204
|
+
|
205
|
+
// return the result
|
206
|
+
return result;
|
207
|
+
}
|
208
|
+
|
209
|
+
/**
|
210
|
+
* @dev Proxies the ether sent to the AccessControl Adapter to the target contract
|
211
|
+
*
|
212
|
+
* @dev Throws if target contract doesn't have the default payable receiver, i.e. doesn't accept ether
|
213
|
+
*/
|
214
|
+
receive() external payable {
|
215
|
+
// delegate to `execute(bytes)`
|
216
|
+
execute(bytes(""));
|
217
|
+
}
|
218
|
+
|
219
|
+
/**
|
220
|
+
* @dev Calls the target contract with the calldata specified in the transaction
|
221
|
+
*
|
222
|
+
* @dev See `execute()` function for details
|
223
|
+
* @dev Use `execute()` function directly if the target contract function signature collides
|
224
|
+
* with any of the AccessControl Adapter functions signature
|
225
|
+
*/
|
226
|
+
fallback() external payable {
|
227
|
+
// msg.data contains full calldata: function selector + encoded function arguments (if any)
|
228
|
+
// delegate to `execute(bytes)`
|
229
|
+
execute(msg.data);
|
230
|
+
}
|
231
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
pragma solidity >=0.4.22;
|
3
|
+
|
4
|
+
import "../AccessControl.sol";
|
5
|
+
|
6
|
+
// Used in AccessControl tests to check if `isSenderInRole` works through the `restrictedTo` modifier
|
7
|
+
contract AccessControlMock is AccessControl {
|
8
|
+
uint32 public constant RESTRICTED_ROLE = 1;
|
9
|
+
event Restricted();
|
10
|
+
constructor(address _owner, uint256 _features) AccessControl(_owner, _features){}
|
11
|
+
function restricted() public restrictedTo(RESTRICTED_ROLE) {
|
12
|
+
emit Restricted();
|
13
|
+
}
|
14
|
+
}
|