@lazy-sol/access-control-upgradeable 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
package/.editorconfig ADDED
@@ -0,0 +1,21 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ [*]
7
+ charset = utf-8
8
+ end_of_line = lf
9
+ indent_style = tab
10
+ indent_size = 2
11
+ insert_final_newline = true
12
+ max_line_length = 120
13
+
14
+ [*.md]
15
+ indent_size = 4
16
+ tab_width = 4
17
+ indent_style = space
18
+ trim_trailing_whitespace = false
19
+
20
+ [package*.json]
21
+ indent_style = space
package/.solcover.js ADDED
@@ -0,0 +1,39 @@
1
+ // https://hardhat.org/plugins/solidity-coverage.html
2
+ // https://github.com/sc-forks/solidity-coverage#config-options
3
+ module.exports = {
4
+ // Ganache only: ganache-core options
5
+ // https://github.com/trufflesuite/ganache-core/tree/master#options
6
+ /*
7
+ providerOptions: {
8
+ host: "localhost",
9
+ network_id: 0xeeeb04de,
10
+ port: 8669,
11
+ default_balance_ether: 10000,
12
+ total_accounts: 35,
13
+ // gasLimit: 0xffffffff,
14
+ gasPrice: 1,
15
+ },
16
+ */
17
+ // Array of contracts or folders (with paths expressed relative to the contracts directory)
18
+ // that should be skipped when doing instrumentation.
19
+ skipFiles: [
20
+ "interfaces",
21
+ "mocks",
22
+ ],
23
+
24
+ // Set default mocha options here, use special reporters etc.
25
+ mocha: {
26
+ // timeout: 100000,
27
+
28
+ // disable mocha timeouts:
29
+ // https://mochajs.org/api/mocha#enableTimeouts
30
+ enableTimeouts: false,
31
+ // https://github.com/mochajs/mocha/issues/3813
32
+ timeout: false,
33
+
34
+ // https://github.com/sc-forks/solidity-coverage/blob/master/docs/advanced.md#skipping-tests
35
+ grep: "@skip-on-coverage", // Find everything with this tag
36
+ invert: true // Run the grep's inverse set.
37
+ },
38
+
39
+ };
@@ -0,0 +1,26 @@
1
+ Licensing inclusion for test & contract code:
2
+
3
+
4
+ The MIT License (MIT)
5
+
6
+ Copyright (c) 2016-2020 zOS Global Limited
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining
9
+ a copy of this software and associated documentation files (the
10
+ "Software"), to deal in the Software without restriction, including
11
+ without limitation the rights to use, copy, modify, merge, publish,
12
+ distribute, sublicense, and/or sell copies of the Software, and to
13
+ permit persons to whom the Software is furnished to do so, subject to
14
+ the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included
17
+ in all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+
package/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017-2024 Basil Gorin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included
14
+ in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Role-based Access Control (RBAC) #
2
+ An [OZ Upgradeable](https://docs.openzeppelin.com/contracts/4.x/api/proxy)
3
+ version of the [RBAC](https://github.com/lazy-sol/access-control)
4
+
5
+ A shortcut to a modular and easily pluggable dapp architecture.
6
+
7
+ Enable the modular plug and play (PnP) architecture for your dapp by incorporating the role-based access control (RBAC)
8
+ into the smart contracts.
9
+
10
+ ## Installation
11
+ ```
12
+ npm i -D @lazy-sol/access-control-upgradeable
13
+ ```
14
+
15
+ ## Further Reading
16
+ See RBAC [README.md](https://github.com/lazy-sol/access-control/blob/master/README.md)
17
+
18
+ (c) 2017–2024 Basil Gorin
@@ -0,0 +1,360 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.2;
3
+
4
+ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
5
+
6
+ /**
7
+ * @title Initializable Role-based Access Control (RBAC) // ERC1967Proxy
8
+ *
9
+ * @notice Access control smart contract provides an API to check
10
+ * if a specific operation is permitted globally and/or
11
+ * if a particular user has a permission to execute it.
12
+ *
13
+ * @notice This contract is inherited by other contracts requiring the role-based access control (RBAC)
14
+ * protection for the restricted access functions
15
+ *
16
+ * @notice It deals with two main entities: features and roles.
17
+ *
18
+ * @notice Features are designed to be used to enable/disable public functions
19
+ * of the smart contract (used by a wide audience).
20
+ * @notice User roles are designed to control the access to restricted functions
21
+ * of the smart contract (used by a limited set of maintainers).
22
+ *
23
+ * @notice Terms "role", "permissions" and "set of permissions" have equal meaning
24
+ * in the documentation text and may be used interchangeably.
25
+ * @notice Terms "permission", "single permission" implies only one permission bit set.
26
+ *
27
+ * @notice Access manager is a special role which allows to grant/revoke other roles.
28
+ * Access managers can only grant/revoke permissions which they have themselves.
29
+ * As an example, access manager with no other roles set can only grant/revoke its own
30
+ * access manager permission and nothing else.
31
+ *
32
+ * @notice Access manager permission should be treated carefully, as a super admin permission:
33
+ * Access manager with even no other permission can interfere with another account by
34
+ * granting own access manager permission to it and effectively creating more powerful
35
+ * permission set than its own.
36
+ *
37
+ * @dev Both current and OpenZeppelin AccessControl implementations feature a similar API
38
+ * to check/know "who is allowed to do this thing".
39
+ * @dev Zeppelin implementation is more flexible:
40
+ * - it allows setting unlimited number of roles, while current is limited to 256 different roles
41
+ * - it allows setting an admin for each role, while current allows having only one global admin
42
+ * @dev Current implementation is more lightweight:
43
+ * - it uses only 1 bit per role, while Zeppelin uses 256 bits
44
+ * - it allows setting up to 256 roles at once, in a single transaction, while Zeppelin allows
45
+ * setting only one role in a single transaction
46
+ *
47
+ * @dev This smart contract is designed to be inherited by other
48
+ * smart contracts which require access control management capabilities.
49
+ *
50
+ * @dev Access manager permission has a bit 255 set.
51
+ * This bit must not be used by inheriting contracts for any other permissions/features.
52
+ *
53
+ * @dev This is an initializable version of the RBAC, based on Zeppelin implementation,
54
+ * it can be used for ERC1967 proxies, as well as for EIP-1167 minimal proxies
55
+ * see https://docs.openzeppelin.com/contracts/4.x/upgradeable
56
+ * see https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable
57
+ * see https://forum.openzeppelin.com/t/uups-proxies-tutorial-solidity-javascript/7786
58
+ * see https://eips.ethereum.org/EIPS/eip-1167
59
+ * see https://docs.openzeppelin.com/contracts/4.x/api/proxy#Clones
60
+ *
61
+ * @author Basil Gorin
62
+ */
63
+ abstract contract InitializableAccessControl is Initializable {
64
+ /**
65
+ * @dev Privileged addresses with defined roles/permissions
66
+ * @dev In the context of ERC20/ERC721 tokens these can be permissions to
67
+ * allow minting or burning tokens, transferring on behalf and so on
68
+ *
69
+ * @dev Maps user address to the permissions bitmask (role), where each bit
70
+ * represents a permission
71
+ * @dev Bitmask 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
72
+ * represents all possible permissions
73
+ * @dev 'This' address mapping represents global features of the smart contract
74
+ *
75
+ * @dev We keep the mapping private to prevent direct writes to it from the inheriting
76
+ * contracts, `getRole()` and `updateRole()` functions should be used instead
77
+ */
78
+ mapping(address => uint256) private userRoles;
79
+
80
+ /**
81
+ * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
82
+ * the amount of storage used by a contract always adds up to the 50.
83
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
84
+ */
85
+ uint256[49] private __gap;
86
+
87
+ /**
88
+ * @notice Access manager is responsible for assigning the roles to users,
89
+ * enabling/disabling global features of the smart contract
90
+ * @notice Access manager can add, remove and update user roles,
91
+ * remove and update global features
92
+ *
93
+ * @dev Role ROLE_ACCESS_MANAGER allows modifying user roles and global features
94
+ * @dev Role ROLE_ACCESS_MANAGER has single bit at position 255 enabled
95
+ */
96
+ uint256 public constant ROLE_ACCESS_MANAGER = 0x8000000000000000000000000000000000000000000000000000000000000000;
97
+
98
+ /**
99
+ * @notice Upgrade manager is responsible for smart contract upgrades,
100
+ * see https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable
101
+ * see https://docs.openzeppelin.com/contracts/4.x/upgradeable
102
+ *
103
+ * @dev Role ROLE_UPGRADE_MANAGER allows passing the _authorizeUpgrade() check
104
+ * @dev Role ROLE_UPGRADE_MANAGER has single bit at position 254 enabled
105
+ */
106
+ uint256 public constant ROLE_UPGRADE_MANAGER = 0x4000000000000000000000000000000000000000000000000000000000000000;
107
+
108
+ /**
109
+ * @dev Bitmask representing all the possible permissions (super admin role)
110
+ * @dev Has all the bits are enabled (2^256 - 1 value)
111
+ */
112
+ uint256 private constant FULL_PRIVILEGES_MASK = type(uint256).max; // before 0.8.0: uint256(-1) overflows to 0xFFFF...
113
+
114
+ /**
115
+ * @dev Fired in updateRole() and updateFeatures()
116
+ *
117
+ * @param operator address which was granted/revoked permissions
118
+ * @param requested permissions requested
119
+ * @param assigned permissions effectively set
120
+ */
121
+ event RoleUpdated(address indexed operator, uint256 requested, uint256 assigned);
122
+
123
+ /**
124
+ * @notice Function modifier making a function defined as public behave as restricted
125
+ * (so that only a pre-configured set of accounts can execute it)
126
+ *
127
+ * @param role the role transaction executor is required to have;
128
+ * the function throws an "access denied" exception if this condition is not met
129
+ */
130
+ modifier restrictedTo(uint256 role) {
131
+ // verify the access permission
132
+ require(isSenderInRole(role), "access denied");
133
+
134
+ // execute the rest of the function
135
+ _;
136
+ }
137
+
138
+ /**
139
+ * @dev Creates/deploys the RBAC implementation to be used in a proxy
140
+ *
141
+ * @dev Note:
142
+ * the implementation is already initialized and
143
+ * `_postConstruct` is not executable on the implementation
144
+ * `_postConstruct` is still available in the context of a proxy
145
+ * and should be executed on the proxy deployment (in the same tx)
146
+ */
147
+ constructor() initializer {}
148
+
149
+ /**
150
+ * @dev Contract initializer, sets the contract owner to have full privileges
151
+ *
152
+ * @dev Can be executed only once, reverts when executed second time
153
+ *
154
+ * @dev IMPORTANT:
155
+ * this function SHOULD be executed during proxy deployment (in the same transaction)
156
+ *
157
+ * @param _owner smart contract owner having full privileges, can be zero
158
+ * @param _features initial features mask of the contract, can be zero
159
+ */
160
+ function _postConstruct(address _owner, uint256 _features) internal virtual onlyInitializing {
161
+ // grant owner full privileges
162
+ __setRole(_owner, FULL_PRIVILEGES_MASK, FULL_PRIVILEGES_MASK);
163
+ // update initial features bitmask
164
+ __setRole(address(this), _features, _features);
165
+ }
166
+
167
+ /**
168
+ * @dev Highest version that has been initialized.
169
+ * Non-zero value means contract was already initialized.
170
+ * @dev see {Initializable}, {reinitializer}.
171
+ *
172
+ * @return highest version that has been initialized
173
+ */
174
+ function getInitializedVersion() public view returns(uint64) {
175
+ // delegate to `_getInitializedVersion`
176
+ return _getInitializedVersion();
177
+ }
178
+
179
+ /**
180
+ * @notice Retrieves globally set of features enabled
181
+ *
182
+ * @dev Effectively reads userRoles role for the contract itself
183
+ *
184
+ * @return 256-bit bitmask of the features enabled
185
+ */
186
+ function features() public view returns (uint256) {
187
+ // features are stored in 'this' address mapping of `userRoles`
188
+ return getRole(address(this));
189
+ }
190
+
191
+ /**
192
+ * @notice Updates set of the globally enabled features (`features`),
193
+ * taking into account sender's permissions
194
+ *
195
+ * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
196
+ * @dev Function is left for backward compatibility with older versions
197
+ *
198
+ * @param _mask bitmask representing a set of features to enable/disable
199
+ */
200
+ function updateFeatures(uint256 _mask) public {
201
+ // delegate call to `updateRole`
202
+ updateRole(address(this), _mask);
203
+ }
204
+
205
+ /**
206
+ * @notice Reads the permissions (role) for a given user from the `userRoles` mapping
207
+ * (privileged addresses with defined roles/permissions)
208
+ * @notice In the context of ERC20/ERC721 tokens these can be permissions to
209
+ * allow minting or burning tokens, transferring on behalf and so on
210
+ *
211
+ * @dev Having a simple getter instead of making the mapping public
212
+ * allows enforcing the encapsulation of the mapping and protects from
213
+ * writing to it directly in the inheriting smart contracts
214
+ *
215
+ * @param operator address of a user to read permissions for,
216
+ * or self address to read global features of the smart contract
217
+ */
218
+ function getRole(address operator) public view returns(uint256) {
219
+ // read the value from `userRoles` and return
220
+ return userRoles[operator];
221
+ }
222
+
223
+ /**
224
+ * @notice Updates set of permissions (role) for a given user,
225
+ * taking into account sender's permissions.
226
+ *
227
+ * @dev Setting role to zero is equivalent to removing an all permissions
228
+ * @dev Setting role to `FULL_PRIVILEGES_MASK` is equivalent to
229
+ * copying senders' permissions (role) to the user
230
+ * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
231
+ *
232
+ * @param operator address of a user to alter permissions for,
233
+ * or self address to alter global features of the smart contract
234
+ * @param role bitmask representing a set of permissions to
235
+ * enable/disable for a user specified
236
+ */
237
+ function updateRole(address operator, uint256 role) public {
238
+ // caller must have a permission to update user roles
239
+ require(isSenderInRole(ROLE_ACCESS_MANAGER), "access denied");
240
+
241
+ // evaluate the role and reassign it
242
+ __setRole(operator, role, _evaluateBy(msg.sender, getRole(operator), role));
243
+ }
244
+
245
+ /**
246
+ * @notice Determines the permission bitmask an operator can set on the
247
+ * target permission set
248
+ * @notice Used to calculate the permission bitmask to be set when requested
249
+ * in `updateRole` and `updateFeatures` functions
250
+ *
251
+ * @dev Calculated based on:
252
+ * 1) operator's own permission set read from userRoles[operator]
253
+ * 2) target permission set - what is already set on the target
254
+ * 3) desired permission set - what do we want set target to
255
+ *
256
+ * @dev Corner cases:
257
+ * 1) Operator is super admin and its permission set is `FULL_PRIVILEGES_MASK`:
258
+ * `desired` bitset is returned regardless of the `target` permission set value
259
+ * (what operator sets is what they get)
260
+ * 2) Operator with no permissions (zero bitset):
261
+ * `target` bitset is returned regardless of the `desired` value
262
+ * (operator has no authority and cannot modify anything)
263
+ *
264
+ * @dev Example:
265
+ * Consider an operator with the permissions bitmask 00001111
266
+ * is about to modify the target permission set 01010101
267
+ * Operator wants to set that permission set to 00110011
268
+ * Based on their role, an operator has the permissions
269
+ * to update only lowest 4 bits on the target, meaning that
270
+ * high 4 bits of the target set in this example is left
271
+ * unchanged and low 4 bits get changed as desired: 01010011
272
+ *
273
+ * @param operator address of the contract operator which is about to set the permissions
274
+ * @param target input set of permissions to operator is going to modify
275
+ * @param desired desired set of permissions operator would like to set
276
+ * @return resulting set of permissions given operator will set
277
+ */
278
+ function _evaluateBy(address operator, uint256 target, uint256 desired) internal view returns (uint256) {
279
+ // read operator's permissions
280
+ uint256 p = getRole(operator);
281
+
282
+ // taking into account operator's permissions,
283
+ // 1) enable the permissions desired on the `target`
284
+ target |= p & desired;
285
+ // 2) disable the permissions desired on the `target`
286
+ target &= FULL_PRIVILEGES_MASK ^ (p & (FULL_PRIVILEGES_MASK ^ desired));
287
+
288
+ // return calculated result
289
+ return target;
290
+ }
291
+
292
+ /**
293
+ * @notice Checks if requested set of features is enabled globally on the contract
294
+ *
295
+ * @param required set of features to check against
296
+ * @return true if all the features requested are enabled, false otherwise
297
+ */
298
+ function isFeatureEnabled(uint256 required) public view returns (bool) {
299
+ // delegate call to `__hasRole`, passing `features` property
300
+ return __hasRole(features(), required);
301
+ }
302
+
303
+ /**
304
+ * @notice Checks if transaction sender `msg.sender` has all the permissions required
305
+ *
306
+ * @param required set of permissions (role) to check against
307
+ * @return true if all the permissions requested are enabled, false otherwise
308
+ */
309
+ function isSenderInRole(uint256 required) public view returns (bool) {
310
+ // delegate call to `isOperatorInRole`, passing transaction sender
311
+ return isOperatorInRole(msg.sender, required);
312
+ }
313
+
314
+ /**
315
+ * @notice Checks if operator has all the permissions (role) required
316
+ *
317
+ * @param operator address of the user to check role for
318
+ * @param required set of permissions (role) to check
319
+ * @return true if all the permissions requested are enabled, false otherwise
320
+ */
321
+ function isOperatorInRole(address operator, uint256 required) public view returns (bool) {
322
+ // delegate call to `__hasRole`, passing operator's permissions (role)
323
+ return __hasRole(getRole(operator), required);
324
+ }
325
+
326
+ /**
327
+ * @dev Sets the `assignedRole` role to the operator, logs both `requestedRole` and `actualRole`
328
+ *
329
+ * @dev Unsafe:
330
+ * provides direct write access to `userRoles` mapping without any security checks,
331
+ * doesn't verify the executor (msg.sender) permissions,
332
+ * must be kept private at all times
333
+ *
334
+ * @param operator address of a user to alter permissions for,
335
+ * or self address to alter global features of the smart contract
336
+ * @param requestedRole bitmask representing a set of permissions requested
337
+ * to be enabled/disabled for a user specified, used only to be logged into event
338
+ * @param assignedRole bitmask representing a set of permissions to
339
+ * enable/disable for a user specified, used to update the mapping and to be logged into event
340
+ */
341
+ function __setRole(address operator, uint256 requestedRole, uint256 assignedRole) private {
342
+ // assign the role to the operator
343
+ userRoles[operator] = assignedRole;
344
+
345
+ // fire an event
346
+ emit RoleUpdated(operator, requestedRole, assignedRole);
347
+ }
348
+
349
+ /**
350
+ * @dev Checks if role `actual` contains all the permissions required `required`
351
+ *
352
+ * @param actual existent role
353
+ * @param required required role
354
+ * @return true if actual has required role (all permissions), false otherwise
355
+ */
356
+ function __hasRole(uint256 actual, uint256 required) private pure returns (bool) {
357
+ // check the bitmask for the role required and return the result
358
+ return actual & required == required;
359
+ }
360
+ }
@@ -0,0 +1,80 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.2;
3
+
4
+ import "./InitializableAccessControl.sol";
5
+ import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
6
+
7
+ /**
8
+ * @title Upgradeable Role-based Access Control (RBAC) // ERC1967Proxy
9
+ *
10
+ * @notice Access control smart contract provides an API to check
11
+ * if a specific operation is permitted globally and/or
12
+ * if a particular user has a permission to execute it.
13
+ *
14
+ * @notice This contract is inherited by other contracts requiring the role-based access control (RBAC)
15
+ * protection for the restricted access functions
16
+ *
17
+ * @notice It deals with two main entities: features and roles.
18
+ *
19
+ * @notice Features are designed to be used to enable/disable public functions
20
+ * of the smart contract (used by a wide audience).
21
+ * @notice User roles are designed to control the access to restricted functions
22
+ * of the smart contract (used by a limited set of maintainers).
23
+ *
24
+ * @notice Terms "role", "permissions" and "set of permissions" have equal meaning
25
+ * in the documentation text and may be used interchangeably.
26
+ * @notice Terms "permission", "single permission" implies only one permission bit set.
27
+ *
28
+ * @notice Access manager is a special role which allows to grant/revoke other roles.
29
+ * Access managers can only grant/revoke permissions which they have themselves.
30
+ * As an example, access manager with no other roles set can only grant/revoke its own
31
+ * access manager permission and nothing else.
32
+ *
33
+ * @notice Access manager permission should be treated carefully, as a super admin permission:
34
+ * Access manager with even no other permission can interfere with another account by
35
+ * granting own access manager permission to it and effectively creating more powerful
36
+ * permission set than its own.
37
+ *
38
+ * @dev Both current and OpenZeppelin AccessControl implementations feature a similar API
39
+ * to check/know "who is allowed to do this thing".
40
+ * @dev Zeppelin implementation is more flexible:
41
+ * - it allows setting unlimited number of roles, while current is limited to 256 different roles
42
+ * - it allows setting an admin for each role, while current allows having only one global admin
43
+ * @dev Current implementation is more lightweight:
44
+ * - it uses only 1 bit per role, while Zeppelin uses 256 bits
45
+ * - it allows setting up to 256 roles at once, in a single transaction, while Zeppelin allows
46
+ * setting only one role in a single transaction
47
+ *
48
+ * @dev This smart contract is designed to be inherited by other
49
+ * smart contracts which require access control management capabilities.
50
+ *
51
+ * @dev Access manager permission has a bit 255 set.
52
+ * This bit must not be used by inheriting contracts for any other permissions/features.
53
+ *
54
+ * @dev This is an upgradeable version of the RBAC, based on Zeppelin implementation for ERC1967,
55
+ * see https://docs.openzeppelin.com/contracts/4.x/upgradeable
56
+ * see https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable
57
+ * see https://forum.openzeppelin.com/t/uups-proxies-tutorial-solidity-javascript/7786
58
+ *
59
+ * @author Basil Gorin
60
+ */
61
+ abstract contract UpgradeableAccessControl is InitializableAccessControl, UUPSUpgradeable {
62
+ /**
63
+ * @notice Returns an address of the implementation smart contract,
64
+ * see ERC1967Upgrade._getImplementation()
65
+ *
66
+ * @return the current implementation address
67
+ */
68
+ function getImplementation() public view virtual returns (address) {
69
+ // delegate to `ERC1967Upgrade._getImplementation()`
70
+ return _getImplementation();
71
+ }
72
+
73
+ /**
74
+ * @inheritdoc UUPSUpgradeable
75
+ */
76
+ function _authorizeUpgrade(address) internal virtual override {
77
+ // caller must have a permission to upgrade the contract
78
+ require(isSenderInRole(ROLE_UPGRADE_MANAGER), "access denied");
79
+ }
80
+ }
@@ -0,0 +1,31 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.2;
3
+
4
+ import "../UpgradeableAccessControl.sol";
5
+
6
+ // Used in UpgradeableAccessControl tests to check if `isSenderInRole` works through the `restrictedTo` modifier
7
+ contract UpgradeableAccessControlMock is UpgradeableAccessControl {
8
+ uint32 public constant RESTRICTED_ROLE = 1;
9
+ event Restricted();
10
+ function restricted() public restrictedTo(RESTRICTED_ROLE) {
11
+ emit Restricted();
12
+ }
13
+ }
14
+
15
+ contract UpgradeableAccessControl1 is UpgradeableAccessControlMock {
16
+ string public version1;
17
+
18
+ function postConstruct(address _owner, uint256 _features) public virtual initializer {
19
+ _postConstruct(_owner, _features);
20
+ version1 = "1";
21
+ }
22
+ }
23
+
24
+ contract UpgradeableAccessControl2 is UpgradeableAccessControlMock {
25
+ string public version2;
26
+
27
+ function postConstruct(address _owner, uint256 _features) public virtual initializer {
28
+ _postConstruct(_owner, _features);
29
+ version2 = "2";
30
+ }
31
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * default Hardhat configuration which uses account mnemonic to derive accounts
3
+ */
4
+
5
+ // Enable Truffle 5 plugin for tests
6
+ // https://hardhat.org/guides/truffle-testing.html
7
+ require("@nomiclabs/hardhat-truffle5");
8
+
9
+ // enable Solidity-coverage
10
+ // https://hardhat.org/plugins/solidity-coverage.html
11
+ require("solidity-coverage");
12
+
13
+ // enable hardhat-gas-reporter
14
+ // https://hardhat.org/plugins/hardhat-gas-reporter.html
15
+ require("hardhat-gas-reporter");
16
+
17
+ // copy compiled Solidity bytecode directly from the NPM dependencies.
18
+ // https://github.com/vgorin/hardhat-dependency-injector
19
+ require("hardhat-dependency-injector");
20
+
21
+ /**
22
+ * @type import('hardhat/config').HardhatUserConfig
23
+ */
24
+ module.exports = {
25
+ defaultNetwork: "hardhat",
26
+ networks: {
27
+ // https://hardhat.org/hardhat-network/
28
+ hardhat: {
29
+ // set networkId to 0xeeeb04de as for all local networks
30
+ chainId: 0xeeeb04de,
31
+ // set the gas price to one for convenient tx costs calculations in tests
32
+ // gasPrice: 1,
33
+ // London hard fork fix: impossible to set gas price lower than baseFeePerGas (875,000,000)
34
+ initialBaseFeePerGas: 0,
35
+ accounts: {
36
+ count: 35,
37
+ },
38
+ },
39
+ },
40
+
41
+ // Configure Solidity compiler
42
+ solidity: {
43
+ // https://hardhat.org/guides/compile-contracts.html
44
+ compilers: [
45
+ {
46
+ version: "0.8.2",
47
+ settings: {
48
+ optimizer: {
49
+ enabled: true,
50
+ runs: 200
51
+ }
52
+ }
53
+ },
54
+ ]
55
+ },
56
+
57
+ // Set default mocha options here, use special reporters etc.
58
+ mocha: {
59
+ // timeout: 100000,
60
+
61
+ // disable mocha timeouts:
62
+ // https://mochajs.org/api/mocha#enableTimeouts
63
+ enableTimeouts: false,
64
+ // https://github.com/mochajs/mocha/issues/3813
65
+ timeout: false,
66
+ },
67
+
68
+ // hardhat-gas-reporter will be disabled by default, use REPORT_GAS environment variable to enable it
69
+ // https://hardhat.org/plugins/hardhat-gas-reporter.html
70
+ gasReporter: {
71
+ enabled: !!(process.env.REPORT_GAS)
72
+ },
73
+
74
+ // copy compiled Solidity bytecode directly from NPM dependencies
75
+ // https://github.com/vgorin/hardhat-dependency-injector
76
+ dependencyInjector: {
77
+ paths: [
78
+ // ERC1967 is used to deploy upgradeable contracts
79
+ "@openzeppelin/contracts/build/contracts/ERC1967Proxy.json",
80
+ ],
81
+ },
82
+ }