@lazy-sol/access-control 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 +92 -0
- package/Role-based Access Control (RBAC) Lifecycle.png +0 -0
- package/hardhat.config.js +32 -3
- package/index.js +19 -0
- package/package.json +6 -6
- package/test/include/rbac.behaviour.js +1 -1
- package/test/ownable_to_rbac_adapter_rbac.js +1 -1
- /package/{test → scripts}/include/features_roles.js +0 -0
package/README.md
CHANGED
@@ -4,6 +4,98 @@ 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
|
+
## Technical Overview
|
8
|
+
|
9
|
+
Role-based Access Control (RBAC), or simply Access Control, is the base parent contract to be inherited by other smart
|
10
|
+
contracts wishing to enable the RBAC feature. It provides an API to check if a specific operation is permitted globally
|
11
|
+
and/or if a particular user has a permission to execute it.
|
12
|
+
|
13
|
+
It deals with two main entities: features and roles. Features are designed to be used to enable/disable public functions
|
14
|
+
of the smart contract (used by a wide audience). User roles are designed to control the access to restricted functions
|
15
|
+
of the smart contract (used by a limited set of maintainers).
|
16
|
+
|
17
|
+
When designing the RBAC-enabled contract, the best practice is to make all public mutative functions controlled with
|
18
|
+
their corresponding feature flags, which can be enabled/disabled during smart contact deployment, setup process, and,
|
19
|
+
optionally, during contract operation.
|
20
|
+
|
21
|
+
Restricted access functions must be controlled by their corresponding user roles/permissions and usually can be executed
|
22
|
+
by the deployer during smart contract deployment and setup process.
|
23
|
+
|
24
|
+
After deployment is complete and smart contract setup is verified the deployer should enable the feature flags and
|
25
|
+
revoke own permissions to control these flags, as well as permissions to execute restricted access functions.
|
26
|
+
|
27
|
+
It is possible that smart contract functionalities are enabled in phases, but the intention is that eventually it is
|
28
|
+
also possible to set the smart contract to be uncontrolled by anyone and be fully decentralized.
|
29
|
+
|
30
|
+
It is also possible that the deployer shares its admin permissions with other addresses during the deployment and setup
|
31
|
+
process, but eventually all these permissions can be revoked from all the addresses involved.
|
32
|
+
|
33
|
+
Following diagram summarizes stated below:
|
34
|
+
|
35
|
+
![Role-based Access Control (RBAC) Lifecycle](Role-based%20Access%20Control%20%28RBAC%29%20Lifecycle.png)
|
36
|
+
Diagram 1. RBAC-enabled smart contract deployment and setup phases. Contract evolves from the fully controlled in the
|
37
|
+
initial phases of the setup process to the fully decentralized and uncontrolled in the end.
|
38
|
+
|
39
|
+
It is important to note that it is not necessary, and not recommended to wait until the last “Setup Complete” phase is
|
40
|
+
executed to consider the protocol fully operational in the mainnet. In fact, the best practice is to do the launch after
|
41
|
+
the deployer permissions are revoked, but there are admin multisig accounts with the full permissions to control the
|
42
|
+
protocol. This kind of approach allows reacting to the security issues, which are more likely to happen in the beginning
|
43
|
+
of the protocol operation.
|
44
|
+
|
45
|
+
## Special Permissions Mapping
|
46
|
+
|
47
|
+
Special permissions mapping, `userRoles`, stores special permissions of the smart contract administrators and helpers.
|
48
|
+
The mapping is a part of AccessControl and is inherited by the smart contracts using it.
|
49
|
+
|
50
|
+
The value stored in the mapping is a 256 bits unsigned integer, each bit of that integer represents a particular
|
51
|
+
permission. We call a set of permissions a role. Usually, roles are defined as 32 bits unsigned integer constants, but
|
52
|
+
extension to 255 bits is possible.
|
53
|
+
|
54
|
+
Permission with the bit 255 set is a special one. It corresponds to the access manager role `ROLE_ACCESS_MANAGER`
|
55
|
+
defined on the Access Control smart contract and allows accounts having that bit set to grant/revoke their permissions
|
56
|
+
to other addresses and to enable/disable corresponding features of the smart contract (to update self address “this”
|
57
|
+
role – see below).
|
58
|
+
|
59
|
+
Self address “this” mapping is a special one. It represents the deployed smart contract itself and defines features
|
60
|
+
enabled on it. Features control what public functions are enabled and how they behave. Usually, features are defined as
|
61
|
+
32 bits unsigned integer constants, but extension to 255 bits is possible.
|
62
|
+
|
63
|
+
Access Control is a shared parent for other smart contracts which are free to use any strategy to introduce their
|
64
|
+
features and roles. Usually, smart contracts use different values for all the features and roles (see the table in the
|
65
|
+
next section).
|
66
|
+
|
67
|
+
Access manager may revoke its own permissions, including the bit 255. Eventually that allows an access manager to let
|
68
|
+
the smart contract “float freely” and be controlled only by the community (via DAO) or by no one at all.
|
69
|
+
|
70
|
+
## Comparing with OpenZeppelin
|
71
|
+
|
72
|
+
Both our and OpenZeppelin Access Control implementations feature a similar API to check/know "who is allowed to do this
|
73
|
+
thing".
|
74
|
+
|
75
|
+
Zeppelin implementation is more flexible:
|
76
|
+
* it allows setting unlimited number of roles, while current is limited to 256 different roles
|
77
|
+
* it allows setting an admin for each role, while current allows having only one global admin
|
78
|
+
|
79
|
+
Our implementation is more lightweight:
|
80
|
+
* it uses only 1 bit per role, while Zeppelin uses 256 bits
|
81
|
+
* it allows setting up to 256 roles at once, in a single transaction, while Zeppelin allows setting only one role in a
|
82
|
+
single transaction
|
83
|
+
|
84
|
+
## Upgradeability
|
85
|
+
|
86
|
+
[Upgradeable Access Control](https://github.com/lazy-sol/access-control-upgradeable) is a Role-based Access Control
|
87
|
+
extension supporting the OpenZeppelin UUPS Proxy upgrades. Smart contracts inheriting from the
|
88
|
+
`UpgradeableAccessControl` can be deployed behind the ERC1967 proxy and will get the upgradeability mechanism setup.
|
89
|
+
|
90
|
+
Upgradeable Access Control introduces another “special” permission bit 254 which is reserved for an upgrade manager role
|
91
|
+
`ROLE_UPGRADE_MANAGER` which is allowed to and is responsible for implementation upgrades of the ERC1967 Proxy.
|
92
|
+
|
93
|
+
Being controlled by the upgrade manager, the upgradeability is also a revocable feature of the smart contract: the
|
94
|
+
`upgradeTo` restricted function access can be revoked from all the admin accounts.
|
95
|
+
|
96
|
+
The best practice is to disable contract upgradeability when the protocol is mature enough and has already proven its
|
97
|
+
security and stability.
|
98
|
+
|
7
99
|
## Installation
|
8
100
|
```
|
9
101
|
npm i -D @lazy-sol/access-control
|
Binary file
|
package/hardhat.config.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
2
|
* default Hardhat configuration which uses account mnemonic to derive accounts
|
3
|
-
* script
|
3
|
+
* script supports the following environment variables set:
|
4
4
|
* - P_KEY1 – mainnet private key, should start with 0x
|
5
5
|
* or
|
6
6
|
* - MNEMONIC1 – mainnet mnemonic, 12 words
|
@@ -53,6 +53,10 @@
|
|
53
53
|
* or
|
54
54
|
* - MNEMONIC84532 – Base Sepolia (testnet) Optimistic Rollup (L2) mnemonic, 12 words
|
55
55
|
*
|
56
|
+
* - P_KEY – Custom network (defined by its JSON-RPC) private key, should start with 0x
|
57
|
+
* or
|
58
|
+
* - MNEMONIC – Custom network (defined by its JSON-RPC) mnemonic, 12 words
|
59
|
+
*
|
56
60
|
* - ALCHEMY_KEY – Alchemy API key
|
57
61
|
* or
|
58
62
|
* - INFURA_KEY – Infura API key (Project ID)
|
@@ -85,6 +89,13 @@ require("hardhat-gas-reporter");
|
|
85
89
|
// https://www.npmjs.com/package/hardhat-deploy
|
86
90
|
require("hardhat-deploy");
|
87
91
|
|
92
|
+
// automatically generate TypeScript bindings for smart contracts while using Hardhat
|
93
|
+
// TypeScript bindings help IDEs to properly recognize compiled contracts' ABIs
|
94
|
+
// https://github.com/dethcrypto/TypeChain/tree/master/packages/hardhat
|
95
|
+
// npm install -D typechain @typechain/hardhat @typechain/truffle-v5
|
96
|
+
// run: npx hardhat typechain
|
97
|
+
// require("@typechain/hardhat");
|
98
|
+
|
88
99
|
// verify environment setup, display warning if required, replace missing values with fakes
|
89
100
|
const FAKE_MNEMONIC = "test test test test test test test test test test test junk";
|
90
101
|
if(!process.env.MNEMONIC1 && !process.env.P_KEY1) {
|
@@ -185,19 +196,26 @@ else if(process.env.P_KEY84531 && !process.env.P_KEY84531.startsWith("0x")) {
|
|
185
196
|
}
|
186
197
|
if(!process.env.MNEMONIC84532 && !process.env.P_KEY84532) {
|
187
198
|
console.warn("neither MNEMONIC84532 nor P_KEY84532 is not set. Base Sepolia (testnet) deployments won't be available");
|
188
|
-
process.env.
|
199
|
+
process.env.MNEMONIC84532 = FAKE_MNEMONIC;
|
189
200
|
}
|
190
201
|
else if(process.env.P_KEY84532 && !process.env.P_KEY84532.startsWith("0x")) {
|
191
202
|
console.warn("P_KEY84532 doesn't start with 0x. Appended 0x");
|
192
203
|
process.env.P_KEY84532 = "0x" + process.env.P_KEY84532;
|
193
204
|
}
|
205
|
+
if(!process.env.MNEMONIC && !process.env.P_KEY) {
|
206
|
+
process.env.MNEMONIC = FAKE_MNEMONIC;
|
207
|
+
}
|
208
|
+
else if(process.env.P_KEY && !process.env.P_KEY.startsWith("0x")) {
|
209
|
+
console.warn("P_KEY doesn't start with 0x. Appended 0x");
|
210
|
+
process.env.P_KEY = "0x" + process.env.P_KEY;
|
211
|
+
}
|
194
212
|
if(!process.env.INFURA_KEY && !process.env.ALCHEMY_KEY) {
|
195
213
|
console.warn("neither INFURA_KEY nor ALCHEMY_KEY is not set. Deployments may not be available");
|
196
214
|
process.env.INFURA_KEY = "";
|
197
215
|
process.env.ALCHEMY_KEY = "";
|
198
216
|
}
|
199
217
|
if(!process.env.ETHERSCAN_KEY) {
|
200
|
-
console.warn("ETHERSCAN_KEY is not set. Deployed smart contract code verification won't be available");
|
218
|
+
console.warn("ETHERSCAN_KEY is not set. Deployed smart contract code verification won't be available on etherscan");
|
201
219
|
process.env.ETHERSCAN_KEY = "";
|
202
220
|
}
|
203
221
|
if(!process.env.POLYSCAN_KEY) {
|
@@ -210,6 +228,7 @@ if(!process.env.BSCSCAN_KEY) {
|
|
210
228
|
}
|
211
229
|
if(!process.env.BASESCAN_KEY) {
|
212
230
|
console.warn("BASESCAN_KEY is not set. Deployed smart contract code verification won't be available on BaseScan");
|
231
|
+
process.env.BASESCAN_KEY = "";
|
213
232
|
}
|
214
233
|
|
215
234
|
/**
|
@@ -303,6 +322,10 @@ module.exports = {
|
|
303
322
|
url: get_endpoint_url("base_sepolia"),
|
304
323
|
accounts: get_accounts(process.env.P_KEY84532, process.env.MNEMONIC84532),
|
305
324
|
},
|
325
|
+
custom: {
|
326
|
+
url: get_endpoint_url(),
|
327
|
+
accounts: get_accounts(process.env.P_KEY, process.env.MNEMONIC),
|
328
|
+
},
|
306
329
|
},
|
307
330
|
|
308
331
|
// Configure Solidity compiler
|
@@ -348,6 +371,12 @@ module.exports = {
|
|
348
371
|
]
|
349
372
|
},
|
350
373
|
|
374
|
+
// configure typechain to generate Truffle v5 bindings
|
375
|
+
typechain: {
|
376
|
+
outDir: "typechain",
|
377
|
+
target: "truffle-v5",
|
378
|
+
},
|
379
|
+
|
351
380
|
// Set default mocha options here, use special reporters etc.
|
352
381
|
mocha: {
|
353
382
|
// 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",
|
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": {
|
@@ -11,7 +11,7 @@
|
|
11
11
|
"deploy": "hardhat deploy"
|
12
12
|
},
|
13
13
|
"engines": {
|
14
|
-
"node": ">=
|
14
|
+
"node": ">=18.0.0"
|
15
15
|
},
|
16
16
|
"keywords": [
|
17
17
|
"RBAC",
|
@@ -22,15 +22,15 @@
|
|
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
|
-
"hardhat": "^2.
|
27
|
+
"hardhat": "^2.22.6",
|
28
28
|
"hardhat-deploy": "^0.11.45"
|
29
29
|
},
|
30
30
|
"devDependencies": {
|
31
|
-
"@lazy-sol/zeppelin-test-helpers": "^1.0.
|
31
|
+
"@lazy-sol/zeppelin-test-helpers": "^1.0.1",
|
32
32
|
"hardhat-gas-reporter": "^1.0.10",
|
33
|
-
"solidity-coverage": "^0.8.
|
33
|
+
"solidity-coverage": "^0.8.12"
|
34
34
|
},
|
35
35
|
"overrides": {
|
36
36
|
"tough-cookie": "^4.1.3",
|
File without changes
|