@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 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 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.4",
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.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.16.0",
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.0",
33
+ "@lazy-sol/zeppelin-test-helpers": "^1.0.1",
34
34
  "@openzeppelin/contracts": "^4.9.6",
35
- "hardhat-dependency-injector": "^1.0.0",
35
+ "hardhat-dependency-injector": "^1.0.1",
36
36
  "hardhat-gas-reporter": "^1.0.10",
37
- "solidity-coverage": "^0.8.10"
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/bn_utils");
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("./features_roles");
28
+ } = require("../../scripts/include/features_roles");
29
29
 
30
30
  /**
31
31
  * RBAC core behaviour
@@ -21,7 +21,7 @@ const {
21
21
  const {
22
22
  not,
23
23
  ROLE_UPGRADE_MANAGER,
24
- } = require("./include/features_roles");
24
+ } = require("../scripts/include/features_roles");
25
25
 
26
26
  // deployment routines in use
27
27
  const {
File without changes