@lazy-sol/access-control 1.1.1 → 1.1.4
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/CHANGELOG.md +6 -0
- package/CONTRIBUTING.md +2 -1
- package/LICENSE.txt +1 -0
- package/README.md +10 -1
- package/audits/1.1_Prem_resolution.md +60 -0
- package/audits/1.1_final_Prem.pdf +0 -0
- package/contracts/AccessControl.sol +3 -3
- package/contracts/AccessControlCore.sol +49 -15
- package/contracts/AdapterFactory.sol +5 -2
- package/contracts/OwnableToAccessControlAdapter.sol +75 -26
- package/contracts/mocks/ErrorHelper.sol +14 -0
- package/docs/style_guides.md +2 -1
- package/hardhat.config.js +1 -1
- package/package.json +6 -4
- package/suggested_numeration_and_naming.txt +12 -0
- package/test/adapter_factory.js +9 -3
- package/test/include/deployment_routines.js +36 -19
- package/test/include/rbac.behaviour.js +26 -9
- package/test/ownable_to_rbac_adapter.js +99 -11
- package/test/ownable_to_rbac_adapter_rbac.js +4 -2
- package/ui.html +1 -1
- package/artifacts/contracts/OwnableToAccessControlAdapter.sol/OwnableToAccessControlAdapter.json +0 -285
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
v1.1.3: Prem's audit and its resolution
|
2
|
+
- See the list of issues found and resolved in [the audit resolution doc](./audits/1.1_Prem_resolution.md)
|
3
|
+
- See the audit methodology and issues found in [the original audit report](./audits/1.1_final_Prem.pdf)
|
4
|
+
|
5
|
+
v1.1.2: do not enable full privileges to zero address on construction
|
6
|
+
|
1
7
|
v1.1.1: Role-based Access Control (RBAC) Inspector
|
2
8
|
- Introduced the Role-based Access Control (RBAC) Inspector UI capable of evaluating features and roles
|
3
9
|
for already deployed contracts; supports all EVM-based networks (mainnet, sepolia, etc.)
|
package/CONTRIBUTING.md
CHANGED
package/LICENSE.txt
CHANGED
package/README.md
CHANGED
@@ -4,6 +4,10 @@ A shortcut to a modular and easily pluggable dapp architecture.
|
|
4
4
|
Enable the modular plug and play (PnP) architecture for your dapp by incorporating the role-based access control (RBAC)
|
5
5
|
into the smart contracts.
|
6
6
|
|
7
|
+
## Audit(s)
|
8
|
+
* [v1.1 Audit by Prem, December 23, 2024 – January 20, 2025](./audits/1.1_final_Prem.pdf)
|
9
|
+
* [Resolution: v1.1 Audit by Prem](./audits/1.1_Prem_resolution.md)
|
10
|
+
|
7
11
|
## Technical Overview
|
8
12
|
|
9
13
|
Role-based Access Control (RBAC), or simply Access Control, is the base parent contract to be inherited by other smart
|
@@ -267,9 +271,14 @@ To execute the restricted access function on the target contract via the AccessC
|
|
267
271
|
3. To find **all** the addresses having any permissions, track the `RoleUpdated()` event and evaluate the history
|
268
272
|
of `assiged` roles for every `operator` address
|
269
273
|
* Alternatively, use the [tool](ui.html) which automates the process
|
274
|
+
(see demo [here](https://lazy-sol.github.io/access-control/ui.html))
|
275
|
+
|
276
|
+
## See Also
|
277
|
+
[Upgradeable Role-based Access Control (U-RBAC)](https://github.com/lazy-sol/access-control-upgradeable/blob/master/README.md)
|
270
278
|
|
271
279
|
## Contributing
|
272
280
|
Please see the [Contribution Guide](./CONTRIBUTING.md) document to get understanding on how to report issues,
|
273
281
|
contribute to the source code, fix bugs, introduce new features, etc.
|
274
282
|
|
275
|
-
(c) 2017–2024 Basil Gorin
|
283
|
+
(c) 2017–2024 Basil Gorin
|
284
|
+
(c) 2024–2025 Lazy So\[u\]l
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# AccessControl: Smart Contract Audit Report Resolution #
|
2
|
+
|
3
|
+
## Resolution Summary ##
|
4
|
+
|
5
|
+
| ID | | Resolution |
|
6
|
+
|---------|--------------------------------------------------------------------------------------------|--------------|
|
7
|
+
| Major-1 | Low-Level Call in `execute(bytes)` (OwnableToAccessControlAdapter) | Fixed |
|
8
|
+
| Major-2 | Validation of `targetAddress` in `deployNewOwnableToAccessControlAdapter` (AdapterFactory) | Fixed |
|
9
|
+
| Minor-1 | Insufficient Event Logging in `RoleUpdated` | Fixed |
|
10
|
+
| Minor-2 | No Check for `role != 0` in `updateRole` | Mitigated |
|
11
|
+
| Minor-3 | State Variables Could Be Declared immutable (OwnableToAccessControlAdapter) | Fixed |
|
12
|
+
| Notes-1 | Version Constraints with Known Issues | Mitigated |
|
13
|
+
| Notes-2 | Visibility Optimization for Gas Savings | Fixed |
|
14
|
+
| Notes-3 | Potential Improvement for `FULL_PRIVILEGES_MASK` Assignment | Acknowledged |
|
15
|
+
|
16
|
+
For issues which were ignored, acknowledged, mitigated, or fixed differently than suggested by the auditor, see the
|
17
|
+
[Comments](#comments) section below.
|
18
|
+
|
19
|
+
## Comments ##
|
20
|
+
### Major-1. Low-Level Call in `execute(bytes)` (OwnableToAccessControlAdapter) ###
|
21
|
+
Fixed by
|
22
|
+
1. making `target` immutable,
|
23
|
+
2. ensuring it is an already deployed contract in the `OwnableToAccessControlAdapter` constructor:
|
24
|
+
```solidity
|
25
|
+
require(_target.code.length != 0, "EOA");
|
26
|
+
```
|
27
|
+
3. porting `_revert` function from OZ 4.9.6 `@openzeppelin/contracts/utils/Address.sol` library and using it
|
28
|
+
to keep original error messages from the target contract:
|
29
|
+
```solidity
|
30
|
+
_revert(returndata, "execution failed");
|
31
|
+
```
|
32
|
+
|
33
|
+
### Minor-2. No Check for `role != 0` in `updateRole` ###
|
34
|
+
To be used as a parent contract for RBAC-based applications, the `AccessControl` contract is originally designed to be
|
35
|
+
lightweight, which was improved even further in version 1.1 by introducing the `AccessControlCore` contract.
|
36
|
+
|
37
|
+
To minimise contract size, we minimise the number of public functions exposed and use a single public function
|
38
|
+
`updateRole` to add, modify, and delete permissions, including self-revoke.
|
39
|
+
|
40
|
+
Mitigated by adding an explicit and very well noticeable comment in the SolDoc for `updateRole` function.
|
41
|
+
|
42
|
+
### Notes-1. Version Constraints with Known Issues ###
|
43
|
+
To allow the use as a parent contract for a wide range of RBAC-based applications, and serve as a Solidity library,
|
44
|
+
we try to keep pragma constraint as low as possible. This approach maximizes compatibility.
|
45
|
+
|
46
|
+
Mitigated by updating the compiler version to 0.8.28 in `hardhat.config.js`.
|
47
|
+
|
48
|
+
### Notes-2. Visibility Optimization for Gas Savings
|
49
|
+
Visibility modifier was changed from `public` to `external` for functions `isFeatureEnabled`, `isSenderInRole`,
|
50
|
+
`isOperatorInRole`, `updateFeatures`, `updateRole`, `updateAccessRole`, and `deployNewOwnableToAccessControlAdapter`.
|
51
|
+
|
52
|
+
Functions `features`, and `getRole` remain public to be accessible in inheriting contracts.
|
53
|
+
|
54
|
+
### Notes-3. Potential Improvement for `FULL_PRIVILEGES_MASK` Assignment ###
|
55
|
+
Role-based Access Control (RBAC) library, and its AccessControl* contracts are designed to be a long-term, but still
|
56
|
+
temporary solution for the projects evolving in the direction of the fully decentralized operation. RBAC Lifecycle
|
57
|
+
assumes that all the permissions are eventually either fully revoked from external participants, or are fully
|
58
|
+
transitioned to the DAO governance smart contract.
|
59
|
+
|
60
|
+
Thus, further improvements to `FULL_PRIVILEGES_MASK` assignments and management are out of scope for the RBAC library.
|
Binary file
|
@@ -71,7 +71,7 @@ abstract contract AccessControl is AccessControlCore {
|
|
71
71
|
* @param required set of features to check against
|
72
72
|
* @return true if all the features requested are enabled, false otherwise
|
73
73
|
*/
|
74
|
-
function isFeatureEnabled(uint256 required)
|
74
|
+
function isFeatureEnabled(uint256 required) external view returns (bool) {
|
75
75
|
// delegate to internal `_isFeatureEnabled`
|
76
76
|
return _isFeatureEnabled(required);
|
77
77
|
}
|
@@ -82,7 +82,7 @@ abstract contract AccessControl is AccessControlCore {
|
|
82
82
|
* @param required set of permissions (role) to check against
|
83
83
|
* @return true if all the permissions requested are enabled, false otherwise
|
84
84
|
*/
|
85
|
-
function isSenderInRole(uint256 required)
|
85
|
+
function isSenderInRole(uint256 required) external view returns (bool) {
|
86
86
|
// delegate to internal `_isSenderInRole`
|
87
87
|
return _isSenderInRole(required);
|
88
88
|
}
|
@@ -94,7 +94,7 @@ abstract contract AccessControl is AccessControlCore {
|
|
94
94
|
* @param required set of permissions (role) to check
|
95
95
|
* @return true if all the permissions requested are enabled, false otherwise
|
96
96
|
*/
|
97
|
-
function isOperatorInRole(address operator, uint256 required)
|
97
|
+
function isOperatorInRole(address operator, uint256 required) external view returns (bool) {
|
98
98
|
// delegate to internal `_isOperatorInRole`
|
99
99
|
return _isOperatorInRole(operator, required);
|
100
100
|
}
|
@@ -107,11 +107,12 @@ abstract contract AccessControlCore {
|
|
107
107
|
/**
|
108
108
|
* @dev Fired in updateRole() and updateFeatures()
|
109
109
|
*
|
110
|
+
* @param by address which has granted/revoked permissions to operator
|
110
111
|
* @param operator address which was granted/revoked permissions
|
111
112
|
* @param requested permissions requested
|
112
113
|
* @param assigned permissions effectively set
|
113
114
|
*/
|
114
|
-
event RoleUpdated(address indexed operator, uint256 requested, uint256 assigned);
|
115
|
+
event RoleUpdated(address indexed by, address indexed operator, uint256 requested, uint256 assigned);
|
115
116
|
|
116
117
|
/**
|
117
118
|
* @notice Function modifier making a function defined as public behave as restricted
|
@@ -135,8 +136,11 @@ abstract contract AccessControlCore {
|
|
135
136
|
* @param _features initial features mask of the contract, can be zero
|
136
137
|
*/
|
137
138
|
constructor(address _owner, uint256 _features) { // visibility modifier is required to be compilable with 0.6.x
|
138
|
-
//
|
139
|
-
|
139
|
+
// if there is a request to set owner (zero address owner means no owner)
|
140
|
+
if(_owner != address(0)) {
|
141
|
+
// grant owner full privileges
|
142
|
+
__setRole(_owner, FULL_PRIVILEGES_MASK, FULL_PRIVILEGES_MASK);
|
143
|
+
}
|
140
144
|
// update initial features bitmask
|
141
145
|
__setRole(address(this), _features, _features);
|
142
146
|
}
|
@@ -148,7 +152,7 @@ abstract contract AccessControlCore {
|
|
148
152
|
*
|
149
153
|
* @return 256-bit bitmask of the features enabled
|
150
154
|
*/
|
151
|
-
function features() public view returns
|
155
|
+
function features() public view returns(uint256) {
|
152
156
|
// features are stored in 'this' address mapping of `userRoles`
|
153
157
|
return getRole(address(this));
|
154
158
|
}
|
@@ -162,9 +166,9 @@ abstract contract AccessControlCore {
|
|
162
166
|
*
|
163
167
|
* @param _mask bitmask representing a set of features to enable/disable
|
164
168
|
*/
|
165
|
-
function updateFeatures(uint256 _mask)
|
166
|
-
// delegate
|
167
|
-
|
169
|
+
function updateFeatures(uint256 _mask) external {
|
170
|
+
// delegate to internal `_updateRole()`
|
171
|
+
_updateRole(address(this), _mask);
|
168
172
|
}
|
169
173
|
|
170
174
|
/**
|
@@ -189,22 +193,30 @@ abstract contract AccessControlCore {
|
|
189
193
|
* @notice Updates set of permissions (role) for a given user,
|
190
194
|
* taking into account sender's permissions.
|
191
195
|
*
|
192
|
-
* @dev Setting role to zero is equivalent to removing
|
196
|
+
* @dev Setting role to zero is equivalent to removing all the permissions
|
193
197
|
* @dev Setting role to `FULL_PRIVILEGES_MASK` is equivalent to
|
194
198
|
* copying senders' permissions (role) to the user
|
195
199
|
* @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
|
196
200
|
*
|
201
|
+
* ╔════════════════════════════════════════════════════════════════════════╗
|
202
|
+
* ║ WARNING: RISK OF ACCIDENTAL SELF-REVOKE ║
|
203
|
+
* ╠════════════════════════════════════════════════════════════════════════╣
|
204
|
+
* ║ updateRole function is used to add, update, and delete permissions, ║
|
205
|
+
* ║ as well as to revoke and self-revoke all the permissions ║
|
206
|
+
* ║ ║
|
207
|
+
* ║ updateRole(msg.sender, 0) executed by a super admin themselves ║
|
208
|
+
* ║ revokes super admin permissions forever if there is no other super ║
|
209
|
+
* ║ admin set prior to updateRole(msg.sender, 0) call ║
|
210
|
+
* ╚════════════════════════════════════════════════════════════════════════╝
|
211
|
+
*
|
197
212
|
* @param operator address of a user to alter permissions for,
|
198
213
|
* or self address to alter global features of the smart contract
|
199
214
|
* @param role bitmask representing a set of permissions to
|
200
215
|
* enable/disable for a user specified
|
201
216
|
*/
|
202
|
-
function updateRole(address operator, uint256 role)
|
203
|
-
//
|
204
|
-
|
205
|
-
|
206
|
-
// evaluate the role and reassign it
|
207
|
-
__setRole(operator, role, _evaluateBy(msg.sender, getRole(operator), role));
|
217
|
+
function updateRole(address operator, uint256 role) external {
|
218
|
+
// delegate to internal `_updateRole()`
|
219
|
+
_updateRole(operator, role);
|
208
220
|
}
|
209
221
|
|
210
222
|
/**
|
@@ -316,6 +328,28 @@ abstract contract AccessControlCore {
|
|
316
328
|
return __hasRole(getRole(operator), required);
|
317
329
|
}
|
318
330
|
|
331
|
+
/**
|
332
|
+
* @dev Updates set of permissions (role) for a given user,
|
333
|
+
* taking into account sender's permissions.
|
334
|
+
*
|
335
|
+
* @dev Setting role to zero is equivalent to removing all the permissions
|
336
|
+
* @dev Setting role to `FULL_PRIVILEGES_MASK` is equivalent to
|
337
|
+
* copying senders' permissions (role) to the user
|
338
|
+
* @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
|
339
|
+
*
|
340
|
+
* @param operator address of a user to alter permissions for,
|
341
|
+
* or self address to alter global features of the smart contract
|
342
|
+
* @param role bitmask representing a set of permissions to
|
343
|
+
* enable/disable for a user specified
|
344
|
+
*/
|
345
|
+
function _updateRole(address operator, uint256 role) internal {
|
346
|
+
// caller must have a permission to update user roles
|
347
|
+
_requireSenderInRole(ROLE_ACCESS_MANAGER);
|
348
|
+
|
349
|
+
// evaluate the role and reassign it
|
350
|
+
__setRole(operator, role, _evaluateBy(msg.sender, getRole(operator), role));
|
351
|
+
}
|
352
|
+
|
319
353
|
/**
|
320
354
|
* @dev Sets the `assignedRole` role to the operator, logs both `requestedRole` and `actualRole`
|
321
355
|
*
|
@@ -336,7 +370,7 @@ abstract contract AccessControlCore {
|
|
336
370
|
userRoles[operator] = assignedRole;
|
337
371
|
|
338
372
|
// fire an event
|
339
|
-
emit RoleUpdated(operator, requestedRole, assignedRole);
|
373
|
+
emit RoleUpdated(msg.sender, operator, requestedRole, assignedRole);
|
340
374
|
}
|
341
375
|
|
342
376
|
/**
|
@@ -1,5 +1,5 @@
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
2
|
-
pragma solidity >=0.8.
|
2
|
+
pragma solidity >=0.8.20;
|
3
3
|
|
4
4
|
import "./OwnableToAccessControlAdapter.sol";
|
5
5
|
|
@@ -39,7 +39,10 @@ contract AdapterFactory {
|
|
39
39
|
* @param targetAddress OZ Ownable target address to bind OwnableToAccessControlAdapter to
|
40
40
|
* @return address of the newly deployed OwnableToAccessControlAdapter contract
|
41
41
|
*/
|
42
|
-
function deployNewOwnableToAccessControlAdapter(address targetAddress)
|
42
|
+
function deployNewOwnableToAccessControlAdapter(address targetAddress) external returns(address) {
|
43
|
+
// verify the inputs
|
44
|
+
require(targetAddress != address(0), "zero address");
|
45
|
+
require(targetAddress.code.length != 0, "EOA");
|
43
46
|
// verify sender is a target owner
|
44
47
|
require(Ownable(targetAddress).owner() == msg.sender, "not an owner");
|
45
48
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
2
2
|
// breaking changes in .call() (0.5.0)
|
3
3
|
// allow .call{}() (0.6.2)
|
4
|
-
pragma solidity >=0.8.
|
4
|
+
pragma solidity >=0.8.20;
|
5
5
|
|
6
6
|
import "./AccessControl.sol";
|
7
7
|
|
@@ -71,7 +71,7 @@ contract OwnableToAccessControlAdapter is AccessControlCore {
|
|
71
71
|
*
|
72
72
|
* @dev Target contract must transfer its ownership to the AccessControl Adapter
|
73
73
|
*/
|
74
|
-
address public target;
|
74
|
+
address public immutable target;
|
75
75
|
|
76
76
|
/**
|
77
77
|
* @dev Access roles mapping stores the roles required to access the functions on the
|
@@ -95,16 +95,17 @@ contract OwnableToAccessControlAdapter is AccessControlCore {
|
|
95
95
|
* @param selector selector of the function which corresponding access role was updated
|
96
96
|
* @param role effective required role to execute the function defined by the selector
|
97
97
|
*/
|
98
|
-
event AccessRoleUpdated(bytes4 selector, uint256 role);
|
98
|
+
event AccessRoleUpdated(bytes4 indexed selector, uint256 role);
|
99
99
|
|
100
100
|
/**
|
101
101
|
* @dev Logs function execution result on the target if the execution completed successfully
|
102
102
|
*
|
103
103
|
* @param selector selector of the function which was executed on the target contract
|
104
|
+
* @param roleRequired role that was required to execute the function requested
|
104
105
|
* @param data full calldata payload passed to the target contract (includes the 4-bytes selector)
|
105
106
|
* @param result execution response from the target contract
|
106
107
|
*/
|
107
|
-
event ExecutionComplete(bytes4 selector, bytes data, bytes result);
|
108
|
+
event ExecutionComplete(bytes4 indexed selector, uint256 roleRequired, bytes data, bytes result);
|
108
109
|
|
109
110
|
/**
|
110
111
|
* @dev Deploys an AccessControl Adapter binding it to the target OZ Ownable contract,
|
@@ -116,6 +117,7 @@ contract OwnableToAccessControlAdapter is AccessControlCore {
|
|
116
117
|
constructor(address _target, address _owner) AccessControlCore(_owner, 0) { // visibility modifier is required to be compilable with 0.6.x
|
117
118
|
// verify the inputs
|
118
119
|
require(_target != address(0), "zero address");
|
120
|
+
require(_target.code.length != 0, "EOA");
|
119
121
|
|
120
122
|
// initialize internal contract state
|
121
123
|
target = _target;
|
@@ -132,9 +134,9 @@ contract OwnableToAccessControlAdapter is AccessControlCore {
|
|
132
134
|
* @param role role required to execute this function, or zero to disable
|
133
135
|
* access to the specified function for everyone
|
134
136
|
*/
|
135
|
-
function updateAccessRole(string memory signature, uint256 role)
|
136
|
-
// delegate to `
|
137
|
-
|
137
|
+
function updateAccessRole(string memory signature, uint256 role) external {
|
138
|
+
// delegate to internal `_updateAccessRole(bytes4, uint256)`
|
139
|
+
__updateAccessRole(bytes4(keccak256(bytes(signature))), role);
|
138
140
|
}
|
139
141
|
|
140
142
|
/**
|
@@ -148,7 +150,23 @@ contract OwnableToAccessControlAdapter is AccessControlCore {
|
|
148
150
|
* @param role role required to execute this function, or zero to disable
|
149
151
|
* access to the specified function for everyone
|
150
152
|
*/
|
151
|
-
function updateAccessRole(bytes4 selector, uint256 role)
|
153
|
+
function updateAccessRole(bytes4 selector, uint256 role) external {
|
154
|
+
// delegate to internal `_updateAccessRole(bytes4, uint256)`
|
155
|
+
__updateAccessRole(selector, role);
|
156
|
+
}
|
157
|
+
|
158
|
+
/**
|
159
|
+
* @dev Updates the access role required to execute the function defined by its selector
|
160
|
+
* on the target contract
|
161
|
+
*
|
162
|
+
* @dev More on function signatures and selectors: https://docs.soliditylang.org/en/develop/abi-spec.html
|
163
|
+
*
|
164
|
+
* @param selector function selector on the target contract, for example
|
165
|
+
* 0xf2fde38b selector corresponds to the "transferOwnership(address)" function
|
166
|
+
* @param role role required to execute this function, or zero to disable
|
167
|
+
* access to the specified function for everyone
|
168
|
+
*/
|
169
|
+
function __updateAccessRole(bytes4 selector, uint256 role) private {
|
152
170
|
// verify the access permission
|
153
171
|
_requireSenderInRole(ROLE_ACCESS_ROLES_MANAGER);
|
154
172
|
|
@@ -175,34 +193,25 @@ contract OwnableToAccessControlAdapter is AccessControlCore {
|
|
175
193
|
*/
|
176
194
|
function execute(bytes memory data) public payable returns(bytes memory) {
|
177
195
|
// extract the selector (first 4 bytes as bytes4) using assembly
|
178
|
-
bytes4 selector;
|
179
|
-
assembly {
|
180
|
-
// load the first word after the length field
|
181
|
-
selector := mload(add(data, 32))
|
182
|
-
}
|
196
|
+
bytes4 selector = data.length == 0? bytes4(0x00000000): __extractSelector(data);
|
183
197
|
|
184
|
-
//
|
185
|
-
|
186
|
-
// if the data is present, we're executing some real function and must do a security check
|
187
|
-
if(data.length != 0) {
|
188
|
-
// determine the role required to access the function
|
189
|
-
uint256 roleRequired = accessRoles[selector];
|
198
|
+
// determine the role required to access the function
|
199
|
+
uint256 roleRequired = accessRoles[selector];
|
190
200
|
|
191
|
-
|
192
|
-
|
201
|
+
// verify function access role was already set
|
202
|
+
require(roleRequired != 0, "access role not set");
|
193
203
|
|
194
|
-
|
195
|
-
|
196
|
-
}
|
204
|
+
// verify the access permission
|
205
|
+
_requireSenderInRole(roleRequired);
|
197
206
|
|
198
207
|
// execute the call on the target
|
199
208
|
(bool success, bytes memory result) = address(target).call{value: msg.value}(data);
|
200
209
|
|
201
210
|
// verify the execution completed successfully
|
202
|
-
|
211
|
+
__requireSuccessfulCall(success, result);
|
203
212
|
|
204
213
|
// emit an event
|
205
|
-
emit ExecutionComplete(selector, data, result);
|
214
|
+
emit ExecutionComplete(selector, roleRequired, data, result);
|
206
215
|
|
207
216
|
// return the result
|
208
217
|
return result;
|
@@ -230,4 +239,44 @@ contract OwnableToAccessControlAdapter is AccessControlCore {
|
|
230
239
|
// delegate to `execute(bytes)`
|
231
240
|
execute(msg.data);
|
232
241
|
}
|
242
|
+
|
243
|
+
/// @dev Extracts first 4 bytes from the input, throwing if input is less than 4 bytes long
|
244
|
+
function __extractSelector(bytes memory data) private pure returns(bytes4) {
|
245
|
+
// verify data has at least 4 bytes to read
|
246
|
+
require(data.length >= 4, "bad selector");
|
247
|
+
// extract the selector (first 4 bytes as bytes4) using assembly
|
248
|
+
bytes4 selector;
|
249
|
+
assembly {
|
250
|
+
// load the first word after the length field
|
251
|
+
selector := mload(add(data, 32))
|
252
|
+
}
|
253
|
+
// return whatever we've loaded from the memory
|
254
|
+
return selector;
|
255
|
+
}
|
256
|
+
|
257
|
+
/// @dev Mimics the require(success, string(returndata))
|
258
|
+
function __requireSuccessfulCall(bool success, bytes memory returndata) private pure {
|
259
|
+
// if operation was not successful
|
260
|
+
if(!success) {
|
261
|
+
// revert, trying to deliver original error message from the low-level call,
|
262
|
+
// and falling back to "execution failed" if low-level call returned no message
|
263
|
+
__revert(returndata, "execution failed");
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
/// @dev Copied as is from OZ 4.9.6 @openzeppelin/contracts/utils/Address.sol::_revert(bytes,string)
|
268
|
+
function __revert(bytes memory returndata, string memory errorMessage) private pure {
|
269
|
+
// Look for revert reason and bubble it up if present
|
270
|
+
if(returndata.length > 0) {
|
271
|
+
// The easiest way to bubble the revert reason is using memory via assembly
|
272
|
+
/// @solidity memory-safe-assembly
|
273
|
+
assembly {
|
274
|
+
let returndata_size := mload(returndata)
|
275
|
+
revert(add(32, returndata), returndata_size)
|
276
|
+
}
|
277
|
+
}
|
278
|
+
else {
|
279
|
+
revert(errorMessage);
|
280
|
+
}
|
281
|
+
}
|
233
282
|
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
pragma solidity ^0.8.4;
|
3
|
+
|
4
|
+
contract ErrorHelper {
|
5
|
+
address public owner;
|
6
|
+
|
7
|
+
function transferOwnership(address _owner) public {
|
8
|
+
owner = _owner;
|
9
|
+
}
|
10
|
+
|
11
|
+
function throwError(string memory message) public pure {
|
12
|
+
require(false, message);
|
13
|
+
}
|
14
|
+
}
|
package/docs/style_guides.md
CHANGED
package/hardhat.config.js
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lazy-sol/access-control",
|
3
|
-
"version": "1.1.
|
3
|
+
"version": "1.1.4",
|
4
4
|
"description": "Enable the modular plug and play (PnP) architecture for your dapp by incorporating the role-based access control (RBAC) into the smart contracts",
|
5
5
|
"main": "index.js",
|
6
6
|
"scripts": {
|
@@ -22,16 +22,18 @@
|
|
22
22
|
"author": "Basil Gorin",
|
23
23
|
"license": "MIT",
|
24
24
|
"devDependencies": {
|
25
|
-
"@lazy-sol/a-missing-gem": "^1.0.
|
25
|
+
"@lazy-sol/a-missing-gem": "^1.0.12",
|
26
26
|
"@lazy-sol/zeppelin-test-helpers": "^1.0.5",
|
27
27
|
"@nomiclabs/hardhat-truffle5": "^2.0.7",
|
28
|
-
"hardhat": "^2.
|
28
|
+
"hardhat": "^2.25.0",
|
29
29
|
"hardhat-deploy": "^0.11.45",
|
30
30
|
"hardhat-gas-reporter": "^1.0.10",
|
31
|
-
"solidity-coverage": "^0.8.
|
31
|
+
"solidity-coverage": "^0.8.16"
|
32
32
|
},
|
33
33
|
"overrides": {
|
34
34
|
"axios": ">=1.7.5",
|
35
|
+
"cookie": ">=0.7.0",
|
36
|
+
"elliptic": "^6.6.0",
|
35
37
|
"micromatch": "^4.0.8",
|
36
38
|
"tar": "^6.2.1",
|
37
39
|
"tough-cookie": "^4.1.3",
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Report AccessControl
|
2
|
+
add Minor-1 | Insufficient Event Logging in `RoleUpdated` and shift other Minor numbering by one:
|
3
|
+
Minor-2 | No Check for `role != 0` in `updateRole`
|
4
|
+
Minor-3 | State Variables Could Be Declared immutable (OwnableToAccessControlAdapter)
|
5
|
+
|
6
|
+
Report AccessControlUpgradable
|
7
|
+
switch Notes-1. Gas Optimizations and Notes-2. Use Latest Solidity Version (^0.8.26), use the same naming as in AccessControl report:
|
8
|
+
Notes-1 | Version Constraints with Known Issues (may keep the original Use Latest Solidity Version (^0.8.26) name but then need to change to the same in AccessControl report)
|
9
|
+
Notes-2 | Visibility Optimization for Gas Savings
|
10
|
+
add Minor-2 | No Check for `role != 0` in `updateRole`
|
11
|
+
add Notes-3 | Potential Improvement for `FULL_PRIVILEGES_MASK` Assignment and shift Notes-3. Test Coverage by one: Notes-4. Test Coverage
|
12
|
+
|
package/test/adapter_factory.js
CHANGED
@@ -21,7 +21,7 @@ const {
|
|
21
21
|
const {
|
22
22
|
deploy_usdt,
|
23
23
|
deploy_adapter_factory,
|
24
|
-
|
24
|
+
factory_deploy_ownable_to_ac_adapter_pure,
|
25
25
|
} = require("./include/deployment_routines");
|
26
26
|
|
27
27
|
// run AdapterFactory tests
|
@@ -39,13 +39,19 @@ contract("AdapterFactory tests", function(accounts) {
|
|
39
39
|
factory = await deploy_adapter_factory();
|
40
40
|
});
|
41
41
|
|
42
|
+
it("adapter deployment fails if target address is zero address", async function() {
|
43
|
+
await expectRevert(factory_deploy_ownable_to_ac_adapter_pure(a1, factory, ZERO_ADDRESS), "zero address");
|
44
|
+
});
|
45
|
+
it("adapter deployment fails if target address is EOA", async function() {
|
46
|
+
await expectRevert(factory_deploy_ownable_to_ac_adapter_pure(a1, factory, a2), "EOA");
|
47
|
+
});
|
42
48
|
it("adapter deployment fails if executed not by the ownable owner", async function() {
|
43
|
-
await expectRevert(
|
49
|
+
await expectRevert(factory_deploy_ownable_to_ac_adapter_pure(a2, factory, usdt), "not an owner");
|
44
50
|
});
|
45
51
|
describe("adapter deployment succeeds if executed by the ownable owner", async function() {
|
46
52
|
let adapter;
|
47
53
|
beforeEach(async function() {
|
48
|
-
|
54
|
+
adapter = await factory_deploy_ownable_to_ac_adapter_pure(a1, factory, usdt);
|
49
55
|
});
|
50
56
|
it("adapter's target is set correctly", async function() {
|
51
57
|
expect(await adapter.target()).to.be.equal(usdt.address);
|
@@ -6,13 +6,21 @@
|
|
6
6
|
* @returns USDT ERC20 instance
|
7
7
|
*/
|
8
8
|
async function deploy_usdt(a0) {
|
9
|
-
// smart contracts required
|
10
9
|
const USDTContract = artifacts.require("TetherToken");
|
11
|
-
|
12
|
-
// deploy the token and return the reference
|
13
10
|
return await USDTContract.new(0, "Tether USD", "USDT", 6, {from: a0});
|
14
11
|
}
|
15
12
|
|
13
|
+
/**
|
14
|
+
* Deploys ErrorHelper, which helps to throw arbitrary error string, used in tests
|
15
|
+
*
|
16
|
+
* @param a0 deployer account
|
17
|
+
* @return ErrorHelper instance
|
18
|
+
*/
|
19
|
+
async function deploy_error_helper(a0) {
|
20
|
+
const ErrorHelper = artifacts.require("ErrorHelper");
|
21
|
+
return await ErrorHelper.new({from: a0});
|
22
|
+
}
|
23
|
+
|
16
24
|
/**
|
17
25
|
* Deploys AccessControl contract
|
18
26
|
*
|
@@ -22,10 +30,7 @@ async function deploy_usdt(a0) {
|
|
22
30
|
* @returns AccessControl instance
|
23
31
|
*/
|
24
32
|
async function deploy_access_control(a0, owner = a0, features = 0) {
|
25
|
-
// deploy AccessControlMock
|
26
33
|
const AccessControlMock = artifacts.require("AccessControlMock");
|
27
|
-
|
28
|
-
// deploy and return the instance
|
29
34
|
return await AccessControlMock.new(owner, features, {from: a0});
|
30
35
|
}
|
31
36
|
|
@@ -67,9 +72,7 @@ async function deploy_ownable_to_ac_adapter(a0, target) {
|
|
67
72
|
* @returns OwnableToAccessControlAdapter instance
|
68
73
|
*/
|
69
74
|
async function deploy_no_deps_ownable_to_ac_adapter(a0, target) {
|
70
|
-
// artifacts in use
|
71
75
|
const OwnableToAccessControlAdapter = artifacts.require("OwnableToAccessControlAdapter");
|
72
|
-
// deploy and return the deployd instance
|
73
76
|
return await OwnableToAccessControlAdapter.new(target.address || target, a0, {from: a0});
|
74
77
|
}
|
75
78
|
|
@@ -80,15 +83,14 @@ async function deploy_no_deps_ownable_to_ac_adapter(a0, target) {
|
|
80
83
|
* @returns AdapterFactory instance
|
81
84
|
*/
|
82
85
|
async function deploy_adapter_factory(a0) {
|
83
|
-
// artifacts in use
|
84
86
|
const AdapterFactory = artifacts.require("AdapterFactory");
|
85
|
-
// deploy and return the deployd instance
|
86
87
|
return await AdapterFactory.new(a0? {from: a0}: undefined);
|
87
88
|
}
|
88
89
|
|
89
90
|
/**
|
90
91
|
* Deploys OwnableToAccessControlAdapter via the AdapterFactory
|
91
92
|
* Deploys the AdapterFactory and target Ownable if required
|
93
|
+
* Transfers ownership from the target to the adapter
|
92
94
|
*
|
93
95
|
* @param a0 deployer address, target owner, required
|
94
96
|
* @param factory AdapterFactory instance or address, optional
|
@@ -113,15 +115,7 @@ async function factory_deploy_ownable_to_ac_adapter(a0, factory, target) {
|
|
113
115
|
}
|
114
116
|
|
115
117
|
// deploy the adapter via the AdapterFactory
|
116
|
-
const
|
117
|
-
const {
|
118
|
-
adapterAddress,
|
119
|
-
ownableTargetAddress,
|
120
|
-
} = receipt.logs.find(log => log.event === "NewOwnableToAccessControlAdapterDeployed").args;
|
121
|
-
|
122
|
-
// connect to the adapter
|
123
|
-
const OwnableToAccessControlAdapter = artifacts.require("OwnableToAccessControlAdapter");
|
124
|
-
const adapter = await OwnableToAccessControlAdapter.at(adapterAddress);
|
118
|
+
const adapter = await factory_deploy_ownable_to_ac_adapter_pure(a0, factory, target);
|
125
119
|
|
126
120
|
// transfer ownership to the adapter
|
127
121
|
await target.transferOwnership(adapter.address, {from: a0});
|
@@ -130,12 +124,35 @@ async function factory_deploy_ownable_to_ac_adapter(a0, factory, target) {
|
|
130
124
|
return {factory, target, adapter};
|
131
125
|
}
|
132
126
|
|
127
|
+
/**
|
128
|
+
* Deploys OwnableToAccessControlAdapter via the AdapterFactory
|
129
|
+
*
|
130
|
+
* @param a0 deployer address, target owner, required
|
131
|
+
* @param factory AdapterFactory instance, required
|
132
|
+
* @param target Ownable instance or address, required
|
133
|
+
* @returns OwnableToAccessControlAdapter instance
|
134
|
+
*/
|
135
|
+
async function factory_deploy_ownable_to_ac_adapter_pure(a0, factory, target) {
|
136
|
+
// deploy the adapter via the AdapterFactory
|
137
|
+
const receipt = await factory.deployNewOwnableToAccessControlAdapter(target.address || target, {from: a0});
|
138
|
+
const {
|
139
|
+
adapterAddress,
|
140
|
+
ownableTargetAddress,
|
141
|
+
} = receipt.logs.find(log => log.event === "NewOwnableToAccessControlAdapterDeployed").args;
|
142
|
+
|
143
|
+
// connect to the adapter and return the result
|
144
|
+
const OwnableToAccessControlAdapter = artifacts.require("OwnableToAccessControlAdapter");
|
145
|
+
return await OwnableToAccessControlAdapter.at(adapterAddress);
|
146
|
+
}
|
147
|
+
|
133
148
|
// export public deployment API
|
134
149
|
module.exports = {
|
135
150
|
deploy_usdt,
|
151
|
+
deploy_error_helper,
|
136
152
|
deploy_access_control,
|
137
153
|
deploy_no_deps_ownable_to_ac_adapter,
|
138
154
|
deploy_ownable_to_ac_adapter,
|
139
155
|
deploy_adapter_factory,
|
140
156
|
factory_deploy_ownable_to_ac_adapter,
|
157
|
+
factory_deploy_ownable_to_ac_adapter_pure,
|
141
158
|
}
|
@@ -59,23 +59,34 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
59
59
|
beforeEach(async function() {
|
60
60
|
access_control = await deployment_fn.call(this, a0, owner, features);
|
61
61
|
});
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
if(owner !== ZERO_ADDRESS) {
|
63
|
+
it('"RoleUpdated(owner)" event is emitted correctly', async function() {
|
64
|
+
await expectEvent.inConstruction(access_control, "RoleUpdated", {
|
65
|
+
by: a0,
|
66
|
+
operator: owner,
|
67
|
+
requested: FULL_PRIVILEGES_MASK,
|
68
|
+
assigned: FULL_PRIVILEGES_MASK,
|
69
|
+
});
|
67
70
|
});
|
68
|
-
}
|
71
|
+
}
|
69
72
|
it('"RoleUpdated(this)" event is emitted correctly', async function() {
|
70
73
|
await expectEvent.inConstruction(access_control, "RoleUpdated", {
|
74
|
+
by: a0,
|
71
75
|
operator: access_control.address,
|
72
76
|
requested: features,
|
73
77
|
assigned: features,
|
74
78
|
});
|
75
79
|
});
|
76
|
-
|
77
|
-
|
78
|
-
|
80
|
+
if(owner !== ZERO_ADDRESS) {
|
81
|
+
it("owners' role is set correctly", async function() {
|
82
|
+
expect(await access_control.getRole(owner)).to.be.bignumber.that.equals(FULL_PRIVILEGES_MASK);
|
83
|
+
});
|
84
|
+
}
|
85
|
+
else {
|
86
|
+
it("owners' role is not set", async function() {
|
87
|
+
expect(await access_control.getRole(owner)).to.be.bignumber.that.equals("0");
|
88
|
+
});
|
89
|
+
}
|
79
90
|
it("features are set correctly", async function() {
|
80
91
|
expect(await access_control.features()).to.be.bignumber.that.equals(features);
|
81
92
|
});
|
@@ -125,6 +136,7 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
125
136
|
});
|
126
137
|
it('"RoleUpdated" event', async function() {
|
127
138
|
expectEvent(receipt, "RoleUpdated", {
|
139
|
+
by,
|
128
140
|
operator: to_fn(to),
|
129
141
|
requested: set,
|
130
142
|
assigned: set,
|
@@ -148,6 +160,7 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
148
160
|
});
|
149
161
|
it('"RoleUpdated" event', async function() {
|
150
162
|
expectEvent(receipt, "RoleUpdated", {
|
163
|
+
by,
|
151
164
|
operator: to_fn(to),
|
152
165
|
requested: not(remove),
|
153
166
|
assigned: not(remove),
|
@@ -173,6 +186,7 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
173
186
|
});
|
174
187
|
it('"RoleUpdated" event', async function() {
|
175
188
|
expectEvent(receipt, "RoleUpdated", {
|
189
|
+
by,
|
176
190
|
operator: to_fn(to),
|
177
191
|
requested: set,
|
178
192
|
assigned: "0",
|
@@ -197,6 +211,7 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
197
211
|
});
|
198
212
|
it('"RoleUpdated" event', async function() {
|
199
213
|
expectEvent(receipt, "RoleUpdated", {
|
214
|
+
by,
|
200
215
|
operator: to_fn(to),
|
201
216
|
requested: not(remove),
|
202
217
|
assigned: MAX_UINT256,
|
@@ -228,6 +243,7 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
228
243
|
});
|
229
244
|
it('"RoleUpdated" event', async function() {
|
230
245
|
expectEvent(receipt, "RoleUpdated", {
|
246
|
+
by,
|
231
247
|
operator: to_fn(to),
|
232
248
|
requested: set,
|
233
249
|
assigned: role.and(set),
|
@@ -252,6 +268,7 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
252
268
|
});
|
253
269
|
it('"RoleUpdated" event', async function() {
|
254
270
|
expectEvent(receipt, "RoleUpdated", {
|
271
|
+
by,
|
255
272
|
operator: to_fn(to),
|
256
273
|
requested: not(remove),
|
257
274
|
assigned: not(role.and(remove)),
|
@@ -17,8 +17,12 @@ const {
|
|
17
17
|
MAX_UINT256,
|
18
18
|
} = constants;
|
19
19
|
|
20
|
+
// BN constants and utilities
|
21
|
+
const {random_bn256} = require("@lazy-sol/a-missing-gem");
|
22
|
+
|
20
23
|
// deployment routines in use
|
21
24
|
const {
|
25
|
+
deploy_error_helper,
|
22
26
|
deploy_ownable_to_ac_adapter,
|
23
27
|
deploy_no_deps_ownable_to_ac_adapter,
|
24
28
|
} = require("./include/deployment_routines");
|
@@ -32,16 +36,27 @@ contract("OwnableToAccessControlAdapter tests", function(accounts) {
|
|
32
36
|
// a1, a2,... – working accounts to perform tests on
|
33
37
|
const [A0, a0, H0, a1, a2, a3] = accounts;
|
34
38
|
|
35
|
-
it("
|
39
|
+
it("adapter deployment fails if target is a zero address", async function() {
|
36
40
|
await expectRevert(deploy_no_deps_ownable_to_ac_adapter(a0, ZERO_ADDRESS), "zero address");
|
37
41
|
});
|
42
|
+
it("adapter deployment fails if target is an EOA", async function() {
|
43
|
+
await expectRevert(deploy_no_deps_ownable_to_ac_adapter(a0, a1), "EOA");
|
44
|
+
});
|
38
45
|
describe("after the Adapter is deployed and target Ownable ownership transferred to the Adapter", function() {
|
39
46
|
let target, adapter;
|
40
47
|
beforeEach(async function() {
|
41
48
|
({target, adapter} = await deploy_ownable_to_ac_adapter(a0));
|
42
49
|
});
|
43
50
|
|
51
|
+
it("it is impossible to send ether before empty selector access role is configured", async function() {
|
52
|
+
await expectRevert(web3.eth.sendTransaction({
|
53
|
+
from: a0,
|
54
|
+
to: adapter.address,
|
55
|
+
value: 1_000_000_000, // 1 gwei
|
56
|
+
}), "access role not set");
|
57
|
+
});
|
44
58
|
it("it is impossible to send ether to the non-payable target contract via the adapter", async function() {
|
59
|
+
await adapter.methods["updateAccessRole(bytes4,uint256)"]("0x00000000", 1, {from: a0});
|
45
60
|
await expectRevert(web3.eth.sendTransaction({
|
46
61
|
from: a0,
|
47
62
|
to: adapter.address,
|
@@ -55,15 +70,16 @@ contract("OwnableToAccessControlAdapter tests", function(accounts) {
|
|
55
70
|
data: target.contract.methods.transferOwnership(a2).encodeABI(),
|
56
71
|
}), "access role not set");
|
57
72
|
});
|
58
|
-
describe("
|
73
|
+
describe("when transferOwnership function access control is configured", function() {
|
59
74
|
const ROLE_OWNERSHIP_MANAGER = 0x00010000;
|
75
|
+
const fn_selector = web3.eth.abi.encodeFunctionSignature("transferOwnership(address)");
|
60
76
|
let receipt;
|
61
77
|
beforeEach(async function() {
|
62
|
-
receipt = await adapter.updateAccessRole("transferOwnership(address)", ROLE_OWNERSHIP_MANAGER, {from: a0});
|
78
|
+
receipt = await adapter.methods["updateAccessRole(string,uint256)"]("transferOwnership(address)", ROLE_OWNERSHIP_MANAGER, {from: a0});
|
63
79
|
});
|
64
80
|
it('"AccessRoleUpdated" event is emitted', async function() {
|
65
81
|
expectEvent(receipt, "AccessRoleUpdated", {
|
66
|
-
selector: web3.
|
82
|
+
selector: web3.utils.padRight(fn_selector, 64),
|
67
83
|
role: "" + ROLE_OWNERSHIP_MANAGER,
|
68
84
|
});
|
69
85
|
});
|
@@ -78,23 +94,95 @@ contract("OwnableToAccessControlAdapter tests", function(accounts) {
|
|
78
94
|
beforeEach(async function() {
|
79
95
|
await adapter.updateRole(a1, ROLE_OWNERSHIP_MANAGER, {from: a0});
|
80
96
|
});
|
81
|
-
it("execution of the transferOwnership function
|
82
|
-
await web3.eth.sendTransaction({
|
97
|
+
it("execution of the transferOwnership non-payable function fails if ether is supplied", async function() {
|
98
|
+
await expectRevert(web3.eth.sendTransaction({
|
83
99
|
from: a1,
|
84
100
|
to: adapter.address,
|
85
101
|
data: target.contract.methods.transferOwnership(a2).encodeABI(),
|
86
|
-
|
87
|
-
|
102
|
+
value: 1_000_000_000, // 1 gwei
|
103
|
+
}), "execution failed");
|
88
104
|
});
|
89
|
-
it("execution of the transferOwnership
|
105
|
+
it("execution of the transferOwnership fails if function selector is corrupted", async function() {
|
90
106
|
await expectRevert(web3.eth.sendTransaction({
|
91
107
|
from: a1,
|
92
108
|
to: adapter.address,
|
93
|
-
data:
|
109
|
+
data: "0x112233",
|
94
110
|
value: 1_000_000_000, // 1 gwei
|
95
|
-
}), "
|
111
|
+
}), "bad selector");
|
112
|
+
});
|
113
|
+
describe("execution of the transferOwnership function succeeds otherwise", function() {
|
114
|
+
let receipt, data;
|
115
|
+
beforeEach(async function() {
|
116
|
+
data = target.contract.methods.transferOwnership(a2).encodeABI();
|
117
|
+
receipt = await web3.eth.sendTransaction({
|
118
|
+
from: a1,
|
119
|
+
to: adapter.address,
|
120
|
+
data: data,
|
121
|
+
});
|
122
|
+
});
|
123
|
+
it('"ExecutionComplete" event is emitted', async function() {
|
124
|
+
await expectEvent.inTransaction(receipt.transactionHash, adapter, "ExecutionComplete", {
|
125
|
+
selector: web3.utils.padRight(fn_selector, 64),
|
126
|
+
roleRequired: ROLE_OWNERSHIP_MANAGER,
|
127
|
+
data: data,
|
128
|
+
result: null,
|
129
|
+
});
|
130
|
+
});
|
131
|
+
it("owner gets transferred correctly", async function() {
|
132
|
+
expect(await target.owner()).to.equal(a2);
|
133
|
+
});
|
96
134
|
});
|
97
135
|
});
|
98
136
|
});
|
137
|
+
describe("configuring access to the underlying functions (updateAccessRole)", function() {
|
138
|
+
const fn_signature = "1234";
|
139
|
+
const fn_selector = web3.eth.abi.encodeFunctionSignature(fn_signature);
|
140
|
+
const access_permission = random_bn256();
|
141
|
+
describe("when setting the access with updateAccessRole(string, uint256)", function() {
|
142
|
+
let receipt;
|
143
|
+
beforeEach(async function() {
|
144
|
+
receipt = await adapter.methods["updateAccessRole(string,uint256)"](fn_signature, access_permission, {from: a0});
|
145
|
+
});
|
146
|
+
it('"AccessRoleUpdated" event is emitted', async function() {
|
147
|
+
expectEvent(receipt, "AccessRoleUpdated", {
|
148
|
+
selector: web3.utils.padRight(fn_selector, 64),
|
149
|
+
role: "" + access_permission,
|
150
|
+
});
|
151
|
+
});
|
152
|
+
it("access permission is updated", async function() {
|
153
|
+
expect(await adapter.accessRoles(fn_selector)).to.be.bignumber.that.equals(access_permission);
|
154
|
+
});
|
155
|
+
});
|
156
|
+
describe("when setting the access with updateAccessRole(bytes4, uint256)", function() {
|
157
|
+
let receipt;
|
158
|
+
beforeEach(async function() {
|
159
|
+
receipt = await adapter.methods["updateAccessRole(bytes4,uint256)"](fn_selector, access_permission, {from: a0});
|
160
|
+
});
|
161
|
+
it('"AccessRoleUpdated" event is emitted', async function() {
|
162
|
+
expectEvent(receipt, "AccessRoleUpdated", {
|
163
|
+
selector: web3.utils.padRight(fn_selector, 64),
|
164
|
+
role: "" + access_permission,
|
165
|
+
});
|
166
|
+
});
|
167
|
+
it("access permission is updated", async function() {
|
168
|
+
expect(await adapter.accessRoles(fn_selector)).to.be.bignumber.that.equals(access_permission);
|
169
|
+
});
|
170
|
+
});
|
171
|
+
});
|
172
|
+
});
|
173
|
+
describe("after the Adapter is deployed, targeting to the ErrorHelper contract (custom execution error check)", function() {
|
174
|
+
let target, adapter;
|
175
|
+
beforeEach(async function() {
|
176
|
+
target = await deploy_error_helper(a0);
|
177
|
+
adapter = await deploy_no_deps_ownable_to_ac_adapter(a0, target);
|
178
|
+
await adapter.methods["updateAccessRole(string,uint256)"]("throwError(string)", 1, {from: a0});
|
179
|
+
await adapter.updateRole(a1, 1, {from: a0});
|
180
|
+
});
|
181
|
+
|
182
|
+
it("custom error string gets propagated from the target through the adapter", async function() {
|
183
|
+
const message = "Hello, World!";
|
184
|
+
const data = target.contract.methods.throwError(message).encodeABI();
|
185
|
+
await expectRevert(adapter.execute(data, {from: a1}), message);
|
186
|
+
});
|
99
187
|
});
|
100
188
|
});
|
@@ -46,12 +46,14 @@ contract("OwnableToAccessControlAdapter: RBAC tests", function(accounts) {
|
|
46
46
|
it("it is impossible to configure the access role from an unauthorized account", async function() {
|
47
47
|
const operator = a1;
|
48
48
|
await adapter.updateRole(operator, not(ROLE_ACCESS_ROLES_MANAGER), {from: a0});
|
49
|
-
await expectRevert(adapter.updateAccessRole("1", 1, {from: operator}), "AccessDenied()");
|
49
|
+
await expectRevert(adapter.methods["updateAccessRole(string,uint256)"]("1", 1, {from: operator}), "AccessDenied()");
|
50
|
+
await expectRevert(adapter.methods["updateAccessRole(bytes4,uint256)"]("0x12345678", 1, {from: operator}), "AccessDenied()");
|
50
51
|
});
|
51
52
|
it("it is possible to configure the access role from the authorized account", async function() {
|
52
53
|
const operator = a1;
|
53
54
|
await adapter.updateRole(operator, ROLE_ACCESS_ROLES_MANAGER, {from: a0});
|
54
|
-
adapter.updateAccessRole("1", 1, {from: operator});
|
55
|
+
adapter.methods["updateAccessRole(string,uint256)"]("1", 1, {from: operator});
|
56
|
+
adapter.methods["updateAccessRole(bytes4,uint256)"]("0x12345678", 1, {from: operator});
|
55
57
|
});
|
56
58
|
});
|
57
59
|
});
|
package/ui.html
CHANGED
@@ -44,7 +44,7 @@
|
|
44
44
|
</div>
|
45
45
|
</div>
|
46
46
|
</fieldset>
|
47
|
-
<div style="position: fixed; left: 0; bottom: 0; margin: 0.1em 0.2em;">© 2024 <a href="https://github.com/lazy-sol/">Lazy So[u]l</a></div>
|
47
|
+
<div style="position: fixed; left: 0; bottom: 0; margin: 0.1em 0.2em;">© 2024–2025 <a href="https://github.com/lazy-sol/">Lazy So[u]l</a></div>
|
48
48
|
<div style="position: fixed; right: 0; bottom: 0; margin: 0.1em 0.2em; font-family: monospace;">
|
49
49
|
[<a href="https://lazy-sol.github.io/advanced-erc20/ui.html">Advanced ERC20</a>]
|
50
50
|
[<a href="https://lazy-sol.github.io/tiny-erc721/ui.html">Tiny ERC721</a>]
|
package/artifacts/contracts/OwnableToAccessControlAdapter.sol/OwnableToAccessControlAdapter.json
DELETED
@@ -1,285 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"_format": "hh-sol-artifact-1",
|
3
|
-
"contractName": "OwnableToAccessControlAdapter",
|
4
|
-
"sourceName": "contracts/OwnableToAccessControlAdapter.sol",
|
5
|
-
"abi": [
|
6
|
-
{
|
7
|
-
"inputs": [
|
8
|
-
{
|
9
|
-
"internalType": "address",
|
10
|
-
"name": "_target",
|
11
|
-
"type": "address"
|
12
|
-
},
|
13
|
-
{
|
14
|
-
"internalType": "address",
|
15
|
-
"name": "_owner",
|
16
|
-
"type": "address"
|
17
|
-
}
|
18
|
-
],
|
19
|
-
"stateMutability": "nonpayable",
|
20
|
-
"type": "constructor"
|
21
|
-
},
|
22
|
-
{
|
23
|
-
"inputs": [],
|
24
|
-
"name": "AccessDenied",
|
25
|
-
"type": "error"
|
26
|
-
},
|
27
|
-
{
|
28
|
-
"anonymous": false,
|
29
|
-
"inputs": [
|
30
|
-
{
|
31
|
-
"indexed": false,
|
32
|
-
"internalType": "bytes4",
|
33
|
-
"name": "selector",
|
34
|
-
"type": "bytes4"
|
35
|
-
},
|
36
|
-
{
|
37
|
-
"indexed": false,
|
38
|
-
"internalType": "uint256",
|
39
|
-
"name": "role",
|
40
|
-
"type": "uint256"
|
41
|
-
}
|
42
|
-
],
|
43
|
-
"name": "AccessRoleUpdated",
|
44
|
-
"type": "event"
|
45
|
-
},
|
46
|
-
{
|
47
|
-
"anonymous": false,
|
48
|
-
"inputs": [
|
49
|
-
{
|
50
|
-
"indexed": false,
|
51
|
-
"internalType": "bytes4",
|
52
|
-
"name": "selector",
|
53
|
-
"type": "bytes4"
|
54
|
-
},
|
55
|
-
{
|
56
|
-
"indexed": false,
|
57
|
-
"internalType": "bytes",
|
58
|
-
"name": "data",
|
59
|
-
"type": "bytes"
|
60
|
-
},
|
61
|
-
{
|
62
|
-
"indexed": false,
|
63
|
-
"internalType": "bytes",
|
64
|
-
"name": "result",
|
65
|
-
"type": "bytes"
|
66
|
-
}
|
67
|
-
],
|
68
|
-
"name": "ExecutionComplete",
|
69
|
-
"type": "event"
|
70
|
-
},
|
71
|
-
{
|
72
|
-
"anonymous": false,
|
73
|
-
"inputs": [
|
74
|
-
{
|
75
|
-
"indexed": true,
|
76
|
-
"internalType": "address",
|
77
|
-
"name": "operator",
|
78
|
-
"type": "address"
|
79
|
-
},
|
80
|
-
{
|
81
|
-
"indexed": false,
|
82
|
-
"internalType": "uint256",
|
83
|
-
"name": "requested",
|
84
|
-
"type": "uint256"
|
85
|
-
},
|
86
|
-
{
|
87
|
-
"indexed": false,
|
88
|
-
"internalType": "uint256",
|
89
|
-
"name": "assigned",
|
90
|
-
"type": "uint256"
|
91
|
-
}
|
92
|
-
],
|
93
|
-
"name": "RoleUpdated",
|
94
|
-
"type": "event"
|
95
|
-
},
|
96
|
-
{
|
97
|
-
"stateMutability": "payable",
|
98
|
-
"type": "fallback"
|
99
|
-
},
|
100
|
-
{
|
101
|
-
"inputs": [],
|
102
|
-
"name": "ROLE_ACCESS_MANAGER",
|
103
|
-
"outputs": [
|
104
|
-
{
|
105
|
-
"internalType": "uint256",
|
106
|
-
"name": "",
|
107
|
-
"type": "uint256"
|
108
|
-
}
|
109
|
-
],
|
110
|
-
"stateMutability": "view",
|
111
|
-
"type": "function"
|
112
|
-
},
|
113
|
-
{
|
114
|
-
"inputs": [],
|
115
|
-
"name": "ROLE_ACCESS_ROLES_MANAGER",
|
116
|
-
"outputs": [
|
117
|
-
{
|
118
|
-
"internalType": "uint256",
|
119
|
-
"name": "",
|
120
|
-
"type": "uint256"
|
121
|
-
}
|
122
|
-
],
|
123
|
-
"stateMutability": "view",
|
124
|
-
"type": "function"
|
125
|
-
},
|
126
|
-
{
|
127
|
-
"inputs": [
|
128
|
-
{
|
129
|
-
"internalType": "bytes4",
|
130
|
-
"name": "",
|
131
|
-
"type": "bytes4"
|
132
|
-
}
|
133
|
-
],
|
134
|
-
"name": "accessRoles",
|
135
|
-
"outputs": [
|
136
|
-
{
|
137
|
-
"internalType": "uint256",
|
138
|
-
"name": "",
|
139
|
-
"type": "uint256"
|
140
|
-
}
|
141
|
-
],
|
142
|
-
"stateMutability": "view",
|
143
|
-
"type": "function"
|
144
|
-
},
|
145
|
-
{
|
146
|
-
"inputs": [
|
147
|
-
{
|
148
|
-
"internalType": "bytes",
|
149
|
-
"name": "data",
|
150
|
-
"type": "bytes"
|
151
|
-
}
|
152
|
-
],
|
153
|
-
"name": "execute",
|
154
|
-
"outputs": [
|
155
|
-
{
|
156
|
-
"internalType": "bytes",
|
157
|
-
"name": "",
|
158
|
-
"type": "bytes"
|
159
|
-
}
|
160
|
-
],
|
161
|
-
"stateMutability": "payable",
|
162
|
-
"type": "function"
|
163
|
-
},
|
164
|
-
{
|
165
|
-
"inputs": [],
|
166
|
-
"name": "features",
|
167
|
-
"outputs": [
|
168
|
-
{
|
169
|
-
"internalType": "uint256",
|
170
|
-
"name": "",
|
171
|
-
"type": "uint256"
|
172
|
-
}
|
173
|
-
],
|
174
|
-
"stateMutability": "view",
|
175
|
-
"type": "function"
|
176
|
-
},
|
177
|
-
{
|
178
|
-
"inputs": [
|
179
|
-
{
|
180
|
-
"internalType": "address",
|
181
|
-
"name": "operator",
|
182
|
-
"type": "address"
|
183
|
-
}
|
184
|
-
],
|
185
|
-
"name": "getRole",
|
186
|
-
"outputs": [
|
187
|
-
{
|
188
|
-
"internalType": "uint256",
|
189
|
-
"name": "",
|
190
|
-
"type": "uint256"
|
191
|
-
}
|
192
|
-
],
|
193
|
-
"stateMutability": "view",
|
194
|
-
"type": "function"
|
195
|
-
},
|
196
|
-
{
|
197
|
-
"inputs": [],
|
198
|
-
"name": "target",
|
199
|
-
"outputs": [
|
200
|
-
{
|
201
|
-
"internalType": "address",
|
202
|
-
"name": "",
|
203
|
-
"type": "address"
|
204
|
-
}
|
205
|
-
],
|
206
|
-
"stateMutability": "view",
|
207
|
-
"type": "function"
|
208
|
-
},
|
209
|
-
{
|
210
|
-
"inputs": [
|
211
|
-
{
|
212
|
-
"internalType": "bytes4",
|
213
|
-
"name": "selector",
|
214
|
-
"type": "bytes4"
|
215
|
-
},
|
216
|
-
{
|
217
|
-
"internalType": "uint256",
|
218
|
-
"name": "role",
|
219
|
-
"type": "uint256"
|
220
|
-
}
|
221
|
-
],
|
222
|
-
"name": "updateAccessRole",
|
223
|
-
"outputs": [],
|
224
|
-
"stateMutability": "nonpayable",
|
225
|
-
"type": "function"
|
226
|
-
},
|
227
|
-
{
|
228
|
-
"inputs": [
|
229
|
-
{
|
230
|
-
"internalType": "string",
|
231
|
-
"name": "signature",
|
232
|
-
"type": "string"
|
233
|
-
},
|
234
|
-
{
|
235
|
-
"internalType": "uint256",
|
236
|
-
"name": "role",
|
237
|
-
"type": "uint256"
|
238
|
-
}
|
239
|
-
],
|
240
|
-
"name": "updateAccessRole",
|
241
|
-
"outputs": [],
|
242
|
-
"stateMutability": "nonpayable",
|
243
|
-
"type": "function"
|
244
|
-
},
|
245
|
-
{
|
246
|
-
"inputs": [
|
247
|
-
{
|
248
|
-
"internalType": "uint256",
|
249
|
-
"name": "_mask",
|
250
|
-
"type": "uint256"
|
251
|
-
}
|
252
|
-
],
|
253
|
-
"name": "updateFeatures",
|
254
|
-
"outputs": [],
|
255
|
-
"stateMutability": "nonpayable",
|
256
|
-
"type": "function"
|
257
|
-
},
|
258
|
-
{
|
259
|
-
"inputs": [
|
260
|
-
{
|
261
|
-
"internalType": "address",
|
262
|
-
"name": "operator",
|
263
|
-
"type": "address"
|
264
|
-
},
|
265
|
-
{
|
266
|
-
"internalType": "uint256",
|
267
|
-
"name": "role",
|
268
|
-
"type": "uint256"
|
269
|
-
}
|
270
|
-
],
|
271
|
-
"name": "updateRole",
|
272
|
-
"outputs": [],
|
273
|
-
"stateMutability": "nonpayable",
|
274
|
-
"type": "function"
|
275
|
-
},
|
276
|
-
{
|
277
|
-
"stateMutability": "payable",
|
278
|
-
"type": "receive"
|
279
|
-
}
|
280
|
-
],
|
281
|
-
"bytecode": "0x608060405234801561001057600080fd5b50604051610a23380380610a2383398101604081905261002f9161012f565b80600061003f82600019806100bb565b61004a3082806100bb565b50506001600160a01b0382166100955760405162461bcd60e51b815260206004820152600c60248201526b7a65726f206164647265737360a01b604482015260640160405180910390fd5b50600180546001600160a01b0319166001600160a01b0392909216919091179055610162565b6001600160a01b0383166000818152602081815260409182902084905581518581529081018490527fe9be537308880e0f56b7d7cfd7abf85f14c4934486d138f848b92a0cbaf659b4910160405180910390a2505050565b80516001600160a01b038116811461012a57600080fd5b919050565b6000806040838503121561014257600080fd5b61014b83610113565b915061015960208401610113565b90509250929050565b6108b2806101716000396000f3fe6080604052600436106100a05760003560e01c8063491d261111610064578063491d2611146101d35780635defb40d146101f3578063ae5b102e14610213578063ae682e2e14610233578063d4b839921461024b578063d5bb7f6714610283576100bf565b806309c5eabe146100ff5780630e82fe25146101285780632b5214161461016357806334e48c9e14610185578063442767331461019d576100bf565b366100bf576100bd604051806020016040528060008152506102a3565b005b6100bd6000368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506102a392505050565b61011261010d36600461064c565b6102a3565b60405161011f91906106ed565b60405180910390f35b34801561013457600080fd5b5061015561014336600461071d565b60026020526000908152604090205481565b60405190815260200161011f565b34801561016f57600080fd5b5030600090815260208190526040902054610155565b34801561019157600080fd5b50610155600160fd1b81565b3480156101a957600080fd5b506101556101b836600461074f565b6001600160a01b031660009081526020819052604090205490565b3480156101df57600080fd5b506100bd6101ee36600461076a565b610411565b3480156101ff57600080fd5b506100bd61020e366004610794565b610477565b34801561021f57600080fd5b506100bd61022e3660046107ed565b61048c565b34801561023f57600080fd5b50610155600160ff1b81565b34801561025757600080fd5b5060015461026b906001600160a01b031681565b6040516001600160a01b03909116815260200161011f565b34801561028f57600080fd5b506100bd61029e366004610809565b6104f5565b602081015181516060919015610323576001600160e01b03198116600090815260026020526040812054908190036103185760405162461bcd60e51b81526020600482015260136024820152721858d8d95cdcc81c9bdb19481b9bdd081cd95d606a1b60448201526064015b60405180910390fd5b61032181610502565b505b60015460405160009182916001600160a01b03909116903490610347908890610822565b60006040518083038185875af1925050503d8060008114610384576040519150601f19603f3d011682016040523d82523d6000602084013e610389565b606091505b5091509150816103ce5760405162461bcd60e51b815260206004820152601060248201526f195e1958dd5d1a5bdb8819985a5b195960821b604482015260640161030f565b7f57a62eca76fc623c92f161d2a4b851851ece707135ce2af1eec256d660571b6d8386836040516104019392919061083e565b60405180910390a1949350505050565b61041e600160fd1b610502565b6001600160e01b03198216600081815260026020908152604091829020849055815192835282018390527fdb8ed917742b49e83acd1322bcaa8f18b1e5f78a70784c43ea14db7ab50e628d910160405180910390a15050565b610488828051906020012082610411565b5050565b610499600160ff1b610502565b61048882826104f0336104c1876001600160a01b031660009081526020819052604090205490565b6001600160a01b0391909116600090815260208190526040902054600019808818821618908716919091171690565b610513565b6104ff308261048c565b50565b6104ff61050e8261056b565b61057d565b6001600160a01b0383166000818152602081815260409182902084905581518581529081018490527fe9be537308880e0f56b7d7cfd7abf85f14c4934486d138f848b92a0cbaf659b4910160405180910390a2505050565b6000610577338361059b565b92915050565b806104ff57604051634ca8886760e01b815260040160405180910390fd5b6001600160a01b038216600090815260208190526040812054821682145b9392505050565b634e487b7160e01b600052604160045260246000fd5b600067ffffffffffffffff808411156105f1576105f16105c0565b604051601f8501601f19908116603f01168101908282118183101715610619576106196105c0565b8160405280935085815286868601111561063257600080fd5b858560208301376000602087830101525050509392505050565b60006020828403121561065e57600080fd5b813567ffffffffffffffff81111561067557600080fd5b8201601f8101841361068657600080fd5b610695848235602084016105d6565b949350505050565b60005b838110156106b85781810151838201526020016106a0565b50506000910152565b600081518084526106d981602086016020860161069d565b601f01601f19169290920160200192915050565b6020815260006105b960208301846106c1565b80356001600160e01b03198116811461071857600080fd5b919050565b60006020828403121561072f57600080fd5b6105b982610700565b80356001600160a01b038116811461071857600080fd5b60006020828403121561076157600080fd5b6105b982610738565b6000806040838503121561077d57600080fd5b61078683610700565b946020939093013593505050565b600080604083850312156107a757600080fd5b823567ffffffffffffffff8111156107be57600080fd5b8301601f810185136107cf57600080fd5b6107de858235602084016105d6565b95602094909401359450505050565b6000806040838503121561080057600080fd5b61078683610738565b60006020828403121561081b57600080fd5b5035919050565b6000825161083481846020870161069d565b9190910192915050565b63ffffffff60e01b8416815260606020820152600061086060608301856106c1565b828103604084015261087281856106c1565b969550505050505056fea26469706673582212201ba377eb6a91d1e61a05796e2c3f7b98c258168e3db52e709dd2c3a003d3eb7864736f6c63430008150033",
|
282
|
-
"deployedBytecode": "0x6080604052600436106100a05760003560e01c8063491d261111610064578063491d2611146101d35780635defb40d146101f3578063ae5b102e14610213578063ae682e2e14610233578063d4b839921461024b578063d5bb7f6714610283576100bf565b806309c5eabe146100ff5780630e82fe25146101285780632b5214161461016357806334e48c9e14610185578063442767331461019d576100bf565b366100bf576100bd604051806020016040528060008152506102a3565b005b6100bd6000368080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506102a392505050565b61011261010d36600461064c565b6102a3565b60405161011f91906106ed565b60405180910390f35b34801561013457600080fd5b5061015561014336600461071d565b60026020526000908152604090205481565b60405190815260200161011f565b34801561016f57600080fd5b5030600090815260208190526040902054610155565b34801561019157600080fd5b50610155600160fd1b81565b3480156101a957600080fd5b506101556101b836600461074f565b6001600160a01b031660009081526020819052604090205490565b3480156101df57600080fd5b506100bd6101ee36600461076a565b610411565b3480156101ff57600080fd5b506100bd61020e366004610794565b610477565b34801561021f57600080fd5b506100bd61022e3660046107ed565b61048c565b34801561023f57600080fd5b50610155600160ff1b81565b34801561025757600080fd5b5060015461026b906001600160a01b031681565b6040516001600160a01b03909116815260200161011f565b34801561028f57600080fd5b506100bd61029e366004610809565b6104f5565b602081015181516060919015610323576001600160e01b03198116600090815260026020526040812054908190036103185760405162461bcd60e51b81526020600482015260136024820152721858d8d95cdcc81c9bdb19481b9bdd081cd95d606a1b60448201526064015b60405180910390fd5b61032181610502565b505b60015460405160009182916001600160a01b03909116903490610347908890610822565b60006040518083038185875af1925050503d8060008114610384576040519150601f19603f3d011682016040523d82523d6000602084013e610389565b606091505b5091509150816103ce5760405162461bcd60e51b815260206004820152601060248201526f195e1958dd5d1a5bdb8819985a5b195960821b604482015260640161030f565b7f57a62eca76fc623c92f161d2a4b851851ece707135ce2af1eec256d660571b6d8386836040516104019392919061083e565b60405180910390a1949350505050565b61041e600160fd1b610502565b6001600160e01b03198216600081815260026020908152604091829020849055815192835282018390527fdb8ed917742b49e83acd1322bcaa8f18b1e5f78a70784c43ea14db7ab50e628d910160405180910390a15050565b610488828051906020012082610411565b5050565b610499600160ff1b610502565b61048882826104f0336104c1876001600160a01b031660009081526020819052604090205490565b6001600160a01b0391909116600090815260208190526040902054600019808818821618908716919091171690565b610513565b6104ff308261048c565b50565b6104ff61050e8261056b565b61057d565b6001600160a01b0383166000818152602081815260409182902084905581518581529081018490527fe9be537308880e0f56b7d7cfd7abf85f14c4934486d138f848b92a0cbaf659b4910160405180910390a2505050565b6000610577338361059b565b92915050565b806104ff57604051634ca8886760e01b815260040160405180910390fd5b6001600160a01b038216600090815260208190526040812054821682145b9392505050565b634e487b7160e01b600052604160045260246000fd5b600067ffffffffffffffff808411156105f1576105f16105c0565b604051601f8501601f19908116603f01168101908282118183101715610619576106196105c0565b8160405280935085815286868601111561063257600080fd5b858560208301376000602087830101525050509392505050565b60006020828403121561065e57600080fd5b813567ffffffffffffffff81111561067557600080fd5b8201601f8101841361068657600080fd5b610695848235602084016105d6565b949350505050565b60005b838110156106b85781810151838201526020016106a0565b50506000910152565b600081518084526106d981602086016020860161069d565b601f01601f19169290920160200192915050565b6020815260006105b960208301846106c1565b80356001600160e01b03198116811461071857600080fd5b919050565b60006020828403121561072f57600080fd5b6105b982610700565b80356001600160a01b038116811461071857600080fd5b60006020828403121561076157600080fd5b6105b982610738565b6000806040838503121561077d57600080fd5b61078683610700565b946020939093013593505050565b600080604083850312156107a757600080fd5b823567ffffffffffffffff8111156107be57600080fd5b8301601f810185136107cf57600080fd5b6107de858235602084016105d6565b95602094909401359450505050565b6000806040838503121561080057600080fd5b61078683610738565b60006020828403121561081b57600080fd5b5035919050565b6000825161083481846020870161069d565b9190910192915050565b63ffffffff60e01b8416815260606020820152600061086060608301856106c1565b828103604084015261087281856106c1565b969550505050505056fea26469706673582212201ba377eb6a91d1e61a05796e2c3f7b98c258168e3db52e709dd2c3a003d3eb7864736f6c63430008150033",
|
283
|
-
"linkReferences": {},
|
284
|
-
"deployedLinkReferences": {}
|
285
|
-
}
|