@lazy-sol/access-control-upgradeable 1.0.5 → 1.0.7

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/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
- ## Further Reading
16
- See RBAC [README.md](https://github.com/lazy-sol/access-control/blob/master/README.md)
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) // ERC1967Proxy
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 ERC1967 proxies, as well as for EIP-1167 minimal proxies
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 private constant FULL_PRIVILEGES_MASK = type(uint256).max; // before 0.8.0: uint256(-1) overflows to 0xFFFF...
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 CHANGED
@@ -9,6 +9,9 @@ const {
9
9
  not,
10
10
  } = require("./scripts/include/features_roles");
11
11
 
12
+ // RBAC behaviours
13
+ const {behavesLikeRBAC} = require("./test/include/rbac.behaviour");
14
+
12
15
  // Re-export the functions
13
16
  module.exports = {
14
17
  ROLE_ACCESS_MANAGER,
@@ -16,4 +19,5 @@ module.exports = {
16
19
  FULL_PRIVILEGES_MASK,
17
20
  or,
18
21
  not,
22
+ behavesLikeRBAC,
19
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazy-sol/access-control-upgradeable",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
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": ">=16.0.0"
14
+ "node": ">=18.0.0"
15
15
  },
16
16
  "keywords": [
17
17
  "RBAC",
@@ -22,15 +22,14 @@
22
22
  "author": "Basil Gorin",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "@lazy-sol/a-missing-gem": "^1.0.4",
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
- "audit": "^0.0.6",
29
- "hardhat": "^2.22.4",
28
+ "hardhat": "^2.22.7",
30
29
  "hardhat-deploy": "^0.11.45"
31
30
  },
32
31
  "devDependencies": {
33
- "@lazy-sol/zeppelin-test-helpers": "^1.0.0",
32
+ "@lazy-sol/zeppelin-test-helpers": "^1.0.5",
34
33
  "@openzeppelin/contracts": "^4.9.6",
35
34
  "hardhat-dependency-injector": "^1.0.1",
36
35
  "hardhat-gas-reporter": "^1.0.10",
@@ -38,6 +37,7 @@
38
37
  },
39
38
  "overrides": {
40
39
  "tough-cookie": "^4.1.3",
41
- "yargs-parser": "^5.0.1"
40
+ "yargs-parser": "^5.0.1",
41
+ "ws": "^8.0.0"
42
42
  }
43
43
  }
@@ -24,7 +24,8 @@ const {
24
24
  // RBAC core features and roles
25
25
  const {
26
26
  not,
27
- ROLE_ACCESS_MANAGER, FULL_PRIVILEGES_MASK,
27
+ ROLE_ACCESS_MANAGER,
28
+ FULL_PRIVILEGES_MASK,
28
29
  } = require("../../scripts/include/features_roles");
29
30
 
30
31
  /**