@lazy-sol/access-control-upgradeable 1.0.4 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +186 -4
- package/contracts/InitializableAccessControl.sol +4 -4
- package/hardhat.config.js +13 -0
- package/index.js +19 -0
- package/package.json +6 -6
- package/test/include/rbac.behaviour.js +2 -2
- package/test/rbac_upgradeable.js +1 -1
- /package/{test → scripts}/include/features_roles.js +0 -0
package/README.md
CHANGED
@@ -1,18 +1,200 @@
|
|
1
|
-
# Role-based Access Control (RBAC) #
|
1
|
+
# Upgradeable Role-based Access Control (U-RBAC) #
|
2
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)
|
3
|
+
version of the [Role-based Access Control (RBAC)](https://github.com/lazy-sol/access-control)
|
4
4
|
|
5
5
|
A shortcut to a modular and easily pluggable dapp architecture.
|
6
6
|
|
7
7
|
Enable the modular plug and play (PnP) architecture for your dapp by incorporating the role-based access control (RBAC)
|
8
8
|
into the smart contracts.
|
9
9
|
|
10
|
+
## Technical Overview
|
11
|
+
|
12
|
+
Role-based Access Control (RBAC), or simply Access Control, is the base parent contract to be inherited by other smart
|
13
|
+
contracts wishing to enable the RBAC feature. It provides an API to check if a specific operation is permitted globally
|
14
|
+
and/or if a particular user has a permission to execute it.
|
15
|
+
|
16
|
+
It deals with two main entities: features and roles. Features are designed to be used to enable/disable public functions
|
17
|
+
of the smart contract (used by a wide audience). User roles are designed to control the access to restricted functions
|
18
|
+
of the smart contract (used by a limited set of maintainers).
|
19
|
+
|
20
|
+
When designing the RBAC-enabled contract, the best practice is to make all public mutative functions controlled with
|
21
|
+
their corresponding feature flags, which can be enabled/disabled during smart contact deployment, setup process, and,
|
22
|
+
optionally, during contract operation.
|
23
|
+
|
24
|
+
Restricted access functions must be controlled by their corresponding user roles/permissions and usually can be executed
|
25
|
+
by the deployer during smart contract deployment and setup process.
|
26
|
+
|
27
|
+
After deployment is complete and smart contract setup is verified the deployer should enable the feature flags and
|
28
|
+
revoke own permissions to control these flags, as well as permissions to execute restricted access functions.
|
29
|
+
|
30
|
+
It is possible that smart contract functionalities are enabled in phases, but the intention is that eventually it is
|
31
|
+
also possible to set the smart contract to be uncontrolled by anyone and be fully decentralized.
|
32
|
+
|
33
|
+
It is also possible that the deployer shares its admin permissions with other addresses during the deployment and setup
|
34
|
+
process, but eventually all these permissions can be revoked from all the addresses involved.
|
35
|
+
|
36
|
+
Following diagram summarizes stated below:
|
37
|
+
|
38
|
+
![Role-based Access Control (RBAC) Lifecycle](Role-based%20Access%20Control%20%28RBAC%29%20Lifecycle.png)
|
39
|
+
Diagram 1. RBAC-enabled smart contract deployment and setup phases. Contract evolves from the fully controlled in the
|
40
|
+
initial phases of the setup process to the fully decentralized and uncontrolled in the end.
|
41
|
+
|
42
|
+
It is important to note that it is not necessary, and not recommended to wait until the last “Setup Complete” phase is
|
43
|
+
executed to consider the protocol fully operational in the mainnet. In fact, the best practice is to do the launch after
|
44
|
+
the deployer permissions are revoked, but there are admin multisig accounts with the full permissions to control the
|
45
|
+
protocol. This kind of approach allows reacting to the security issues, which are more likely to happen in the beginning
|
46
|
+
of the protocol operation.
|
47
|
+
|
48
|
+
## Special Permissions Mapping
|
49
|
+
|
50
|
+
Special permissions mapping, `userRoles`, stores special permissions of the smart contract administrators and helpers.
|
51
|
+
The mapping is a part of AccessControl and is inherited by the smart contracts using it.
|
52
|
+
|
53
|
+
The value stored in the mapping is a 256 bits unsigned integer, each bit of that integer represents a particular
|
54
|
+
permission. We call a set of permissions a role. Usually, roles are defined as 32 bits unsigned integer constants, but
|
55
|
+
extension to 255 bits is possible.
|
56
|
+
|
57
|
+
Permission with the bit 255 set is a special one. It corresponds to the access manager role `ROLE_ACCESS_MANAGER`
|
58
|
+
defined on the Access Control smart contract and allows accounts having that bit set to grant/revoke their permissions
|
59
|
+
to other addresses and to enable/disable corresponding features of the smart contract (to update self address “this”
|
60
|
+
role – see below).
|
61
|
+
|
62
|
+
Self address “this” mapping is a special one. It represents the deployed smart contract itself and defines features
|
63
|
+
enabled on it. Features control what public functions are enabled and how they behave. Usually, features are defined as
|
64
|
+
32 bits unsigned integer constants, but extension to 255 bits is possible.
|
65
|
+
|
66
|
+
Access Control is a shared parent for other smart contracts which are free to use any strategy to introduce their
|
67
|
+
features and roles. Usually, smart contracts use different values for all the features and roles (see the table in the
|
68
|
+
next section).
|
69
|
+
|
70
|
+
Access manager may revoke its own permissions, including the bit 255. Eventually that allows an access manager to let
|
71
|
+
the smart contract “float freely” and be controlled only by the community (via DAO) or by no one at all.
|
72
|
+
|
73
|
+
## Comparing with OpenZeppelin
|
74
|
+
|
75
|
+
Both our and OpenZeppelin Access Control implementations feature a similar API to check/know "who is allowed to do this
|
76
|
+
thing".
|
77
|
+
|
78
|
+
Zeppelin implementation is more flexible:
|
79
|
+
* it allows setting unlimited number of roles, while current is limited to 256 different roles
|
80
|
+
* it allows setting an admin for each role, while current allows having only one global admin
|
81
|
+
|
82
|
+
Our implementation is more lightweight:
|
83
|
+
* it uses only 1 bit per role, while Zeppelin uses 256 bits
|
84
|
+
* it allows setting up to 256 roles at once, in a single transaction, while Zeppelin allows setting only one role in a
|
85
|
+
single transaction
|
86
|
+
|
87
|
+
## Initialization
|
88
|
+
|
89
|
+
Initializable Role-based Access Control (U-RBAC), or simply Initializable Access Control is a Role-based Access Control
|
90
|
+
variant utilizing an initialization function `_postConstruct` instead of the constructor. It follows the
|
91
|
+
[OZ Initializable](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializers) pattern.
|
92
|
+
|
93
|
+
Contracts, inheriting from Initializable Access Control must be deployed via proxies. This doesn't necessarily mean
|
94
|
+
upgradeability, however – immutable EIP-1167 Minimal Proxy can also be used.
|
95
|
+
|
96
|
+
## Upgradeability
|
97
|
+
|
98
|
+
Upgradeable Role-based Access Control (U-RBAC), or simply Upgradeable Access Control is a Role-based Access Control
|
99
|
+
extension supporting the OpenZeppelin UUPS Proxy upgrades. Smart contracts inheriting from the
|
100
|
+
`UpgradeableAccessControl` can be deployed behind the ERC1967 proxy and will get the upgradeability mechanism setup.
|
101
|
+
|
102
|
+
Upgradeable Access Control introduces another “special” permission bit 254 which is reserved for an upgrade manager role
|
103
|
+
`ROLE_UPGRADE_MANAGER` which is allowed to and is responsible for implementation upgrades of the ERC1967 Proxy.
|
104
|
+
|
105
|
+
Being controlled by the upgrade manager, the upgradeability is also a revocable feature of the smart contract: the
|
106
|
+
`upgradeTo` restricted function access can be revoked from all the admin accounts.
|
107
|
+
|
108
|
+
The best practice is to disable contract upgradeability when the protocol is mature enough and has already proven its
|
109
|
+
security and stability.
|
110
|
+
|
10
111
|
## Installation
|
11
112
|
```
|
12
113
|
npm i -D @lazy-sol/access-control-upgradeable
|
13
114
|
```
|
14
115
|
|
15
|
-
##
|
16
|
-
|
116
|
+
## Usage
|
117
|
+
|
118
|
+
### Creating a Restricted Function
|
119
|
+
|
120
|
+
Restricted function is a function with a `public` Solidity modifier access to which is restricted
|
121
|
+
so that only a pre-configured set of accounts can execute it.
|
122
|
+
|
123
|
+
1. Enable role-based access control (RBAC) in a new smart contract
|
124
|
+
by inheriting the RBAC contract from the [AccessControl](./contracts/UpgradeableAccessControl.sol) contract:
|
125
|
+
```solidity
|
126
|
+
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
|
127
|
+
import "@lazy-sol/access-control/contracts/UpgradeableAccessControl.sol";
|
128
|
+
|
129
|
+
/**
|
130
|
+
* @title Simple ERC20 Implementation (Upgreadable)
|
131
|
+
*
|
132
|
+
* @notice Zeppelin based ERC20 implementation with the U-RBAC support
|
133
|
+
*
|
134
|
+
* @author Lazy So[u]l
|
135
|
+
*/
|
136
|
+
contract MyUpgradeableERC20Token is ERC20Upgradeable, UpgradeableAccessControl {
|
137
|
+
|
138
|
+
...
|
139
|
+
|
140
|
+
}
|
141
|
+
```
|
142
|
+
|
143
|
+
2. Define an access control role with the unique integer value:
|
144
|
+
```solidity
|
145
|
+
...
|
146
|
+
|
147
|
+
/**
|
148
|
+
* @notice Token creator is responsible for creating (minting)
|
149
|
+
tokens to an arbitrary address
|
150
|
+
* @dev Role ROLE_TOKEN_CREATOR allows minting tokens
|
151
|
+
(calling `mint` function)
|
152
|
+
*/
|
153
|
+
uint32 public constant ROLE_TOKEN_CREATOR = 0x0001_0000;
|
154
|
+
|
155
|
+
...
|
156
|
+
```
|
157
|
+
|
158
|
+
3. Add the `require(isSenderInRole(ROLE_TOKEN_CREATOR), "access denied")"` check into the function body:
|
159
|
+
```solidity
|
160
|
+
...
|
161
|
+
|
162
|
+
/**
|
163
|
+
* @inheritdoc ERC20Upgradeable
|
164
|
+
*/
|
165
|
+
function _mint(address _to, uint256 _value) internal virtual override {
|
166
|
+
// check if caller has sufficient permissions to mint tokens
|
167
|
+
require(isSenderInRole(ROLE_TOKEN_CREATOR), "access denied");
|
168
|
+
|
169
|
+
// delegate to super implementation
|
170
|
+
super._mint(_to, _value);
|
171
|
+
}
|
172
|
+
|
173
|
+
...
|
174
|
+
```
|
175
|
+
|
176
|
+
Note: you could also use the `restrictedTo` modifier in the function declaration instead of the `require`
|
177
|
+
in the function body if you don't need a custom error message:
|
178
|
+
```solidity
|
179
|
+
...
|
180
|
+
|
181
|
+
/**
|
182
|
+
* @inheritdoc ERC20Upgradeable
|
183
|
+
*/
|
184
|
+
function _mint(address _to, uint256 _value) internal virtual override restrictedTo(ROLE_TOKEN_CREATOR) {
|
185
|
+
// delegate to super implementation
|
186
|
+
super._mint(_to, _value);
|
187
|
+
}
|
188
|
+
|
189
|
+
...
|
190
|
+
```
|
191
|
+
|
192
|
+
Examples:
|
193
|
+
[AdvancedERC20](https://raw.githubusercontent.com/lazy-sol/advanced-erc20/master/contracts/token/AdvancedERC20.sol),
|
194
|
+
[ERC20v1](https://raw.githubusercontent.com/vgorin/solidity-template/master/contracts/token/upgradeable/ERC20v1.sol),
|
195
|
+
[ERC721v1](https://raw.githubusercontent.com/vgorin/solidity-template/master/contracts/token/upgradeable/ERC721v1.sol).
|
196
|
+
|
197
|
+
## See Also
|
198
|
+
[Role-based Access Control (RBAC)](https://github.com/lazy-sol/access-control/blob/master/README.md)
|
17
199
|
|
18
200
|
(c) 2017–2024 Basil Gorin
|
@@ -4,7 +4,7 @@ pragma solidity ^0.8.2;
|
|
4
4
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
5
5
|
|
6
6
|
/**
|
7
|
-
* @title Initializable Role-based Access Control (RBAC)
|
7
|
+
* @title Initializable Role-based Access Control (RBAC)
|
8
8
|
*
|
9
9
|
* @notice Access control smart contract provides an API to check
|
10
10
|
* if a specific operation is permitted globally and/or
|
@@ -51,12 +51,12 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
51
51
|
* This bit must not be used by inheriting contracts for any other permissions/features.
|
52
52
|
*
|
53
53
|
* @dev This is an initializable version of the RBAC, based on Zeppelin implementation,
|
54
|
-
* it can be used for
|
54
|
+
* it can be used for EIP-1167 minimal proxies, for ERC1967 proxies, etc.
|
55
|
+
* see https://docs.openzeppelin.com/contracts/4.x/api/proxy#Clones
|
55
56
|
* see https://docs.openzeppelin.com/contracts/4.x/upgradeable
|
56
57
|
* see https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable
|
57
58
|
* see https://forum.openzeppelin.com/t/uups-proxies-tutorial-solidity-javascript/7786
|
58
59
|
* see https://eips.ethereum.org/EIPS/eip-1167
|
59
|
-
* see https://docs.openzeppelin.com/contracts/4.x/api/proxy#Clones
|
60
60
|
*
|
61
61
|
* @author Basil Gorin
|
62
62
|
*/
|
@@ -109,7 +109,7 @@ abstract contract InitializableAccessControl is Initializable {
|
|
109
109
|
* @dev Bitmask representing all the possible permissions (super admin role)
|
110
110
|
* @dev Has all the bits are enabled (2^256 - 1 value)
|
111
111
|
*/
|
112
|
-
uint256
|
112
|
+
uint256 internal constant FULL_PRIVILEGES_MASK = type(uint256).max; // before 0.8.0: uint256(-1) overflows to 0xFFFF...
|
113
113
|
|
114
114
|
/**
|
115
115
|
* @dev Fired in updateRole() and updateFeatures()
|
package/hardhat.config.js
CHANGED
@@ -18,6 +18,13 @@ require("hardhat-gas-reporter");
|
|
18
18
|
// https://github.com/vgorin/hardhat-dependency-injector
|
19
19
|
require("hardhat-dependency-injector");
|
20
20
|
|
21
|
+
// automatically generate TypeScript bindings for smart contracts while using Hardhat
|
22
|
+
// TypeScript bindings help IDEs to properly recognize compiled contracts' ABIs
|
23
|
+
// https://github.com/dethcrypto/TypeChain/tree/master/packages/hardhat
|
24
|
+
// npm install -D typechain @typechain/hardhat @typechain/truffle-v5
|
25
|
+
// run: npx hardhat typechain
|
26
|
+
// require("@typechain/hardhat");
|
27
|
+
|
21
28
|
/**
|
22
29
|
* @type import('hardhat/config').HardhatUserConfig
|
23
30
|
*/
|
@@ -54,6 +61,12 @@ module.exports = {
|
|
54
61
|
]
|
55
62
|
},
|
56
63
|
|
64
|
+
// configure typechain to generate Truffle v5 bindings
|
65
|
+
typechain: {
|
66
|
+
outDir: "typechain",
|
67
|
+
target: "truffle-v5",
|
68
|
+
},
|
69
|
+
|
57
70
|
// Set default mocha options here, use special reporters etc.
|
58
71
|
mocha: {
|
59
72
|
// timeout: 100000,
|
package/index.js
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
// Import functions from each module
|
2
|
+
|
3
|
+
// RBAC features and roles
|
4
|
+
const {
|
5
|
+
ROLE_ACCESS_MANAGER,
|
6
|
+
ROLE_UPGRADE_MANAGER,
|
7
|
+
FULL_PRIVILEGES_MASK,
|
8
|
+
or,
|
9
|
+
not,
|
10
|
+
} = require("./scripts/include/features_roles");
|
11
|
+
|
12
|
+
// Re-export the functions
|
13
|
+
module.exports = {
|
14
|
+
ROLE_ACCESS_MANAGER,
|
15
|
+
ROLE_UPGRADE_MANAGER,
|
16
|
+
FULL_PRIVILEGES_MASK,
|
17
|
+
or,
|
18
|
+
not,
|
19
|
+
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lazy-sol/access-control-upgradeable",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.6",
|
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,19 +22,19 @@
|
|
22
22
|
"author": "Basil Gorin",
|
23
23
|
"license": "MIT",
|
24
24
|
"dependencies": {
|
25
|
-
"@lazy-sol/a-missing-gem": "^1.0.
|
25
|
+
"@lazy-sol/a-missing-gem": "^1.0.9",
|
26
26
|
"@nomiclabs/hardhat-truffle5": "^2.0.7",
|
27
27
|
"@openzeppelin/contracts-upgradeable": "^4.9.6",
|
28
28
|
"audit": "^0.0.6",
|
29
|
-
"hardhat": "^2.
|
29
|
+
"hardhat": "^2.22.6",
|
30
30
|
"hardhat-deploy": "^0.11.45"
|
31
31
|
},
|
32
32
|
"devDependencies": {
|
33
|
-
"@lazy-sol/zeppelin-test-helpers": "^1.0.
|
33
|
+
"@lazy-sol/zeppelin-test-helpers": "^1.0.1",
|
34
34
|
"@openzeppelin/contracts": "^4.9.6",
|
35
|
-
"hardhat-dependency-injector": "^1.0.
|
35
|
+
"hardhat-dependency-injector": "^1.0.1",
|
36
36
|
"hardhat-gas-reporter": "^1.0.10",
|
37
|
-
"solidity-coverage": "^0.8.
|
37
|
+
"solidity-coverage": "^0.8.12"
|
38
38
|
},
|
39
39
|
"overrides": {
|
40
40
|
"tough-cookie": "^4.1.3",
|
@@ -19,13 +19,13 @@ const {
|
|
19
19
|
const {
|
20
20
|
random_bn255,
|
21
21
|
random_bn256,
|
22
|
-
} = require("@lazy-sol/a-missing-gem
|
22
|
+
} = require("@lazy-sol/a-missing-gem");
|
23
23
|
|
24
24
|
// RBAC core features and roles
|
25
25
|
const {
|
26
26
|
not,
|
27
27
|
ROLE_ACCESS_MANAGER, FULL_PRIVILEGES_MASK,
|
28
|
-
} = require("
|
28
|
+
} = require("../../scripts/include/features_roles");
|
29
29
|
|
30
30
|
/**
|
31
31
|
* RBAC core behaviour
|
package/test/rbac_upgradeable.js
CHANGED
File without changes
|