@lazy-sol/access-control-upgradeable 1.0.2

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/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@lazy-sol/access-control-upgradeable",
3
+ "version": "1.0.2",
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
+ "main": "index.js",
6
+ "scripts": {
7
+ "clean": "hardhat clean",
8
+ "compile": "hardhat compile",
9
+ "test": "hardhat test",
10
+ "coverage": "hardhat coverage",
11
+ "deploy": "hardhat deploy"
12
+ },
13
+ "engines": {
14
+ "node": ">=16.20.0"
15
+ },
16
+ "keywords": [
17
+ "RBAC",
18
+ "Solidity",
19
+ "access control",
20
+ "modular architecture"
21
+ ],
22
+ "author": "Basil Gorin",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@lazy-sol/a-missing-gem": "^1.0.0",
26
+ "@nomiclabs/hardhat-truffle5": "^2.0.7",
27
+ "@openzeppelin/contracts-upgradeable": "^4.9.6",
28
+ "hardhat": "^2.20.1",
29
+ "hardhat-deploy": "^0.11.45"
30
+ },
31
+ "devDependencies": {
32
+ "@openzeppelin/contracts": "^4.9.6",
33
+ "@openzeppelin/test-helpers": "^0.5.16",
34
+ "hardhat-dependency-injector": "^1.0.0",
35
+ "hardhat-gas-reporter": "^1.0.10",
36
+ "solidity-coverage": "^0.8.10"
37
+ },
38
+ "overrides": {
39
+ "tough-cookie": "^4.1.3",
40
+ "yargs-parser": "^5.0.1"
41
+ }
42
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Deploys UpgradeableAccessControl
3
+ *
4
+ * @param a0 smart contract deployer
5
+ * @param version version number to deploy, optional
6
+ * @returns UpgradeableAccessControl instance
7
+ */
8
+ async function deploy_upgradeable_ac_impl(a0, version = 1) {
9
+ // smart contracts required
10
+ const UpgradeableAccessControl = artifacts.require("UpgradeableAccessControl" + (version || ""));
11
+
12
+ // deploy and return
13
+ return await UpgradeableAccessControl.new({from: a0});
14
+ }
15
+
16
+ /**
17
+ * Deploys UpgradeableAccessControl via ERC1967Proxy
18
+ *
19
+ * @param a0 smart contract deployer
20
+ * @param owner smart contract owner, super admin, optional
21
+ * @param features initial smart contract features, optional
22
+ * @param version version number to deploy, optional
23
+ * @returns ERC1967Proxy –> UpgradeableAccessControl instance
24
+ */
25
+ async function deploy_erc1967_upgradeable_ac(a0, owner = a0, features = 0, version = 1) {
26
+ // smart contracts required
27
+ const UpgradeableAccessControl = artifacts.require("UpgradeableAccessControl" + (version || ""));
28
+ const Proxy = artifacts.require("ERC1967Proxy");
29
+
30
+ // deploy the impl
31
+ const impl = await UpgradeableAccessControl.new({from: a0});
32
+
33
+ // prepare the initialization call bytes
34
+ const init_data = impl.contract.methods.postConstruct(owner, features).encodeABI();
35
+
36
+ // deploy proxy, and initialize the impl (inline)
37
+ const proxy = await Proxy.new(impl.address, init_data, {from: a0});
38
+
39
+ // wrap the proxy into the impl ABI
40
+ const ac = await UpgradeableAccessControl.at(proxy.address);
41
+ ac.transactionHash = proxy.transactionHash;
42
+
43
+ // return, proxy, and impl
44
+ return {proxy: ac, implementation: impl};
45
+ }
46
+
47
+ // export public deployment API
48
+ module.exports = {
49
+ deploy_upgradeable_ac_impl,
50
+ deploy_erc1967_upgradeable_ac,
51
+ }
@@ -0,0 +1,41 @@
1
+ // copy and export all the features and roles constants from different contracts
2
+
3
+ // Auxiliary BN stuff
4
+ const BN = web3.utils.BN;
5
+ const TWO = new BN(2);
6
+
7
+ // Access manager is responsible for assigning the roles to users,
8
+ // enabling/disabling global features of the smart contract
9
+ const ROLE_ACCESS_MANAGER = TWO.pow(new BN(255));
10
+
11
+ // Upgrade manager is responsible for smart contract upgrades
12
+ const ROLE_UPGRADE_MANAGER = TWO.pow(new BN(254));
13
+
14
+ // Access Roles manager is responsible for assigning the access roles to functions
15
+ const ROLE_ACCESS_ROLES_MANAGER = TWO.pow(new BN(253));
16
+
17
+ // Bitmask representing all the possible permissions (super admin role)
18
+ const FULL_PRIVILEGES_MASK = TWO.pow(new BN(256)).subn(1);
19
+
20
+ // combine the role (permission set) provided
21
+ function or(...roles) {
22
+ let roles_sum = new BN(0);
23
+ for(let role of roles) {
24
+ roles_sum = roles_sum.or(new BN(role));
25
+ }
26
+ return roles_sum;
27
+ }
28
+
29
+ // negates the role (permission set) provided
30
+ function not(...roles) {
31
+ return FULL_PRIVILEGES_MASK.xor(or(...roles));
32
+ }
33
+
34
+ // export public module API
35
+ module.exports = {
36
+ ROLE_ACCESS_MANAGER,
37
+ ROLE_UPGRADE_MANAGER,
38
+ FULL_PRIVILEGES_MASK,
39
+ or,
40
+ not,
41
+ };
@@ -0,0 +1,315 @@
1
+ // Zeppelin test helpers
2
+ const {
3
+ BN,
4
+ constants,
5
+ expectEvent,
6
+ expectRevert,
7
+ } = require("@openzeppelin/test-helpers");
8
+ const {
9
+ assert,
10
+ expect,
11
+ } = require("chai");
12
+ const {
13
+ ZERO_ADDRESS,
14
+ ZERO_BYTES32,
15
+ MAX_UINT256,
16
+ } = constants;
17
+
18
+ // BN constants and utilities
19
+ const {
20
+ random_bn255,
21
+ random_bn256,
22
+ } = require("@lazy-sol/a-missing-gem/bn_utils");
23
+
24
+ // RBAC core features and roles
25
+ const {
26
+ not,
27
+ ROLE_ACCESS_MANAGER, FULL_PRIVILEGES_MASK,
28
+ } = require("./features_roles");
29
+
30
+ /**
31
+ * RBAC core behaviour
32
+ *
33
+ * @param deployment_fn RBAC contract deployment function
34
+ * @param a0 deployer/admin account
35
+ * @param a1 participant 1
36
+ * @param a2 participant 2
37
+ */
38
+ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
39
+ // define the "players"
40
+ const by = a1;
41
+ const to = a2;
42
+
43
+ describe("deployment and initial state", function() {
44
+ function deploy_and_check(owner, features) {
45
+ let access_control;
46
+ beforeEach(async function() {
47
+ access_control = await deployment_fn.call(this, a0, owner, features);
48
+ });
49
+ it('"RoleUpdated(owner)" event is emitted correctly', async function() {
50
+ await expectEvent.inConstruction(access_control, "RoleUpdated", {
51
+ operator: owner,
52
+ requested: FULL_PRIVILEGES_MASK,
53
+ assigned: FULL_PRIVILEGES_MASK,
54
+ });
55
+ });
56
+ it('"RoleUpdated(this)" event is emitted correctly', async function() {
57
+ await expectEvent.inConstruction(access_control, "RoleUpdated", {
58
+ operator: access_control.address,
59
+ requested: features,
60
+ assigned: features,
61
+ });
62
+ });
63
+ it("owners' role is set correctly", async function() {
64
+ expect(await access_control.getRole(owner)).to.be.bignumber.that.equals(FULL_PRIVILEGES_MASK);
65
+ });
66
+ it("features are set correctly", async function() {
67
+ expect(await access_control.features()).to.be.bignumber.that.equals(features);
68
+ });
69
+ }
70
+
71
+ describe("owner = 0, features = 0", function() {
72
+ deploy_and_check(ZERO_ADDRESS, new BN(0));
73
+ });
74
+ describe("owner = 0, features ≠ 0", function() {
75
+ deploy_and_check(ZERO_ADDRESS, random_bn256());
76
+ });
77
+ describe("owner ≠ 0, features = 0", function() {
78
+ deploy_and_check(a1, new BN(0));
79
+ });
80
+ describe("owner ≠ 0, features ≠ 0", function() {
81
+ deploy_and_check(a1, random_bn256());
82
+ });
83
+ });
84
+ describe("when deployed with not initial features", function() {
85
+ let access_control;
86
+ beforeEach(async function() {
87
+ access_control = await deployment_fn.call(this, a0);
88
+ });
89
+
90
+ function test_suite(write_fn, read_fn, check_fn, to_fn) {
91
+ describe("when performed by ACCESS_MANAGER", function() {
92
+ beforeEach(async function() {
93
+ await access_control.updateRole(by, ROLE_ACCESS_MANAGER, {from: a0});
94
+ });
95
+ describe("when ACCESS_MANAGER has full set of permissions", function() {
96
+ beforeEach(async function() {
97
+ await access_control.updateRole(by, MAX_UINT256, {from: a0});
98
+ });
99
+ describe("what you set", function() {
100
+ let receipt, set;
101
+ beforeEach(async function() {
102
+ // do not touch the highest permission bit (ACCESS_MANAGER permission)
103
+ set = random_bn255();
104
+ receipt = await write_fn(by, to, set);
105
+ });
106
+ describe("is what you get", function() {
107
+ it('"userRoles" value', async function() {
108
+ expect(await read_fn(to)).to.be.bignumber.that.equals(set);
109
+ });
110
+ it("role check (isOperatorInRole/isFeatureEnabled)", async function() {
111
+ expect(await check_fn(to, set)).to.be.true;
112
+ });
113
+ it('"RoleUpdated" event', async function() {
114
+ expectEvent(receipt, "RoleUpdated", {
115
+ operator: to_fn(to),
116
+ requested: set,
117
+ assigned: set,
118
+ });
119
+ });
120
+ });
121
+ });
122
+ describe("what you remove", function() {
123
+ let receipt, remove;
124
+ beforeEach(async function() {
125
+ // do not touch the highest permission bit (ACCESS_MANAGER permission)
126
+ remove = random_bn255();
127
+ receipt = await write_fn(by, to, not(remove));
128
+ });
129
+ describe("is what gets removed", function() {
130
+ it('"userRoles" value', async function() {
131
+ expect(await read_fn(to)).to.be.bignumber.that.equals(not(remove));
132
+ });
133
+ it("role check (isOperatorInRole/isFeatureEnabled)", async function() {
134
+ expect(await check_fn(to, not(remove))).to.be.true;
135
+ });
136
+ it('"RoleUpdated" event', async function() {
137
+ expectEvent(receipt, "RoleUpdated", {
138
+ operator: to_fn(to),
139
+ requested: not(remove),
140
+ assigned: not(remove),
141
+ });
142
+ });
143
+ });
144
+ });
145
+ });
146
+ describe("when ACCESS_MANAGER doesn't have any permissions", function() {
147
+ describe("what you get, independently of what you set", function() {
148
+ let receipt, set;
149
+ beforeEach(async function() {
150
+ // do not touch the highest permission bit (ACCESS_MANAGER permission)
151
+ set = random_bn255();
152
+ receipt = await write_fn(by, to, set);
153
+ });
154
+ describe("is always zero", function() {
155
+ it('"userRoles" value', async function() {
156
+ expect(await read_fn(to)).to.be.bignumber.that.is.zero;
157
+ });
158
+ it("role check (isOperatorInRole/isFeatureEnabled)", async function() {
159
+ expect(await check_fn(to, set)).to.be.false;
160
+ });
161
+ it('"RoleUpdated" event', async function() {
162
+ expectEvent(receipt, "RoleUpdated", {
163
+ operator: to_fn(to),
164
+ requested: set,
165
+ assigned: "0",
166
+ });
167
+ });
168
+ });
169
+ });
170
+ describe("what you get, independently of what you remove", function() {
171
+ let receipt, remove;
172
+ beforeEach(async function() {
173
+ // do not touch the highest permission bit (ACCESS_MANAGER permission)
174
+ remove = random_bn255();
175
+ await write_fn(a0, to, MAX_UINT256);
176
+ receipt = await write_fn(by, to, not(remove));
177
+ });
178
+ describe("is always what you had", function() {
179
+ it('"userRoles" value', async function() {
180
+ expect(await read_fn(to)).to.be.bignumber.that.equals(MAX_UINT256);
181
+ });
182
+ it("role check (isOperatorInRole/isFeatureEnabled)", async function() {
183
+ expect(await check_fn(to, MAX_UINT256)).to.be.true;
184
+ });
185
+ it('"RoleUpdated" event', async function() {
186
+ expectEvent(receipt, "RoleUpdated", {
187
+ operator: to_fn(to),
188
+ requested: not(remove),
189
+ assigned: MAX_UINT256,
190
+ });
191
+ });
192
+ });
193
+ });
194
+ });
195
+ describe("when ACCESS_MANAGER has some permissions", function() {
196
+ let role;
197
+ beforeEach(async function() {
198
+ // do not touch the highest permission bit (ACCESS_MANAGER permission)
199
+ role = random_bn255();
200
+ await access_control.updateRole(by, ROLE_ACCESS_MANAGER.or(role), {from: a0});
201
+ });
202
+ describe("what you get", function() {
203
+ let receipt, set;
204
+ beforeEach(async function() {
205
+ // do not touch the highest permission bit (ACCESS_MANAGER permission)
206
+ set = random_bn255();
207
+ receipt = await write_fn(by, to, set);
208
+ });
209
+ describe("is an intersection of what you set and what you have", function() {
210
+ it('"userRoles" value', async function() {
211
+ expect(await read_fn(to)).to.be.bignumber.that.equals(role.and(set));
212
+ });
213
+ it("role check (isOperatorInRole/isFeatureEnabled)", async function() {
214
+ expect(await check_fn(to, role.and(set))).to.be.true;
215
+ });
216
+ it('"RoleUpdated" event', async function() {
217
+ expectEvent(receipt, "RoleUpdated", {
218
+ operator: to_fn(to),
219
+ requested: set,
220
+ assigned: role.and(set),
221
+ });
222
+ });
223
+ });
224
+ });
225
+ describe("what you remove", function() {
226
+ let receipt, remove;
227
+ beforeEach(async function() {
228
+ // do not touch the highest permission bit (ACCESS_MANAGER permission)
229
+ remove = random_bn255();
230
+ await write_fn(a0, to, MAX_UINT256);
231
+ receipt = await write_fn(by, to, not(remove));
232
+ });
233
+ describe("is an intersection of what you tried to remove and what you have", function() {
234
+ it('"userRoles" value', async function() {
235
+ expect(await read_fn(to)).to.be.bignumber.that.equals(not(role.and(remove)));
236
+ });
237
+ it("role check (isOperatorInRole/isFeatureEnabled)", async function() {
238
+ expect(await check_fn(to, not(role.and(remove)))).to.be.true;
239
+ });
240
+ it('"RoleUpdated" event', async function() {
241
+ expectEvent(receipt, "RoleUpdated", {
242
+ operator: to_fn(to),
243
+ requested: not(remove),
244
+ assigned: not(role.and(remove)),
245
+ });
246
+ });
247
+ });
248
+ });
249
+ });
250
+ describe("ACCESS_MANAGER updates itself", function() {
251
+ beforeEach(async function() {
252
+ // do not touch the highest permission bit (ACCESS_MANAGER permission)
253
+ const role = random_bn255();
254
+ await access_control.updateRole(by, ROLE_ACCESS_MANAGER.or(role), {from: a0});
255
+ });
256
+ it("and degrades to zero with the 99.99% probability in 14 runs", async function() {
257
+ // randomly remove 255 bits of permissions
258
+ for(let i = 0; i < 14; i++) {
259
+ // do not touch the highest permission bit (ACCESS_MANAGER permission)
260
+ const role = random_bn255();
261
+ await access_control.updateRole(by, not(role), {from: by});
262
+ }
263
+ // this may fail with the probability 2^(-14) < 0.01%
264
+ expect(await access_control.getRole(by)).to.be.bignumber.that.equals(ROLE_ACCESS_MANAGER);
265
+ })
266
+ });
267
+ describe("when ACCESS_MANAGER grants ACCESS_MANAGER permission", function() {
268
+ beforeEach(async function() {
269
+ await access_control.updateRole(to, ROLE_ACCESS_MANAGER, {from: by});
270
+ });
271
+ it("operator becomes an ACCESS_MANAGER", async function() {
272
+ expect(await access_control.isOperatorInRole(to, ROLE_ACCESS_MANAGER), "operator").to.be.true;
273
+ expect(await access_control.isSenderInRole(ROLE_ACCESS_MANAGER, {from: to}), "sender").to.be.true;
274
+ });
275
+ });
276
+ describe("when ACCESS_MANAGER revokes ACCESS_MANAGER permission from itself", function() {
277
+ beforeEach(async function() {
278
+ await access_control.updateRole(by, 0, {from: by});
279
+ });
280
+ it("operator ceases to be an ACCESS_MANAGER", async function() {
281
+ expect(await access_control.isOperatorInRole(by, ROLE_ACCESS_MANAGER), "operator").to.be.false;
282
+ expect(await access_control.isSenderInRole(ROLE_ACCESS_MANAGER, {from: by}), "sender").to.be.false;
283
+ });
284
+ });
285
+ });
286
+ describe("otherwise (no ACCESS_MANAGER permission)", function() {
287
+ it("updateFeatures reverts", async function() {
288
+ await expectRevert(access_control.updateFeatures(1, {from: by}), "access denied");
289
+ });
290
+ it("updateRole reverts", async function() {
291
+ await expectRevert(access_control.updateRole(to, 1, {from: by}), "access denied");
292
+ });
293
+ });
294
+ }
295
+
296
+ // run two test suites to test get/set role and get/set features
297
+ test_suite(
298
+ async(by, to, set) => await access_control.updateRole(to, set, {from: by}),
299
+ async(op) => await access_control.getRole(op),
300
+ async(op, role) => await access_control.isOperatorInRole(op, role),
301
+ (to) => to
302
+ );
303
+ test_suite(
304
+ async(by, to, set) => await access_control.updateFeatures(set, {from: by}),
305
+ async(op) => await access_control.features(),
306
+ async(op, role) => await access_control.isFeatureEnabled(role),
307
+ (to) => access_control.address
308
+ );
309
+ });
310
+ }
311
+
312
+ // export the RBAC core behaviour
313
+ module.exports = {
314
+ behavesLikeRBAC,
315
+ }
@@ -0,0 +1,59 @@
1
+ // AccessControlUpgradeable (U-RBAC) Core Tests
2
+
3
+ // Zeppelin test helpers
4
+ const {
5
+ BN,
6
+ constants,
7
+ expectEvent,
8
+ expectRevert,
9
+ } = require("@openzeppelin/test-helpers");
10
+ const {
11
+ assert,
12
+ expect,
13
+ } = require("chai");
14
+ const {
15
+ ZERO_ADDRESS,
16
+ ZERO_BYTES32,
17
+ MAX_UINT256,
18
+ } = constants;
19
+
20
+ // import the core RBAC behaviour to use
21
+ const {
22
+ behavesLikeRBAC,
23
+ } = require("./include/rbac.behaviour");
24
+
25
+ // deployment routines in use
26
+ const {
27
+ deploy_upgradeable_ac_impl,
28
+ deploy_erc1967_upgradeable_ac,
29
+ } = require("./include/deployment_routines");
30
+
31
+ // RBAC proxy instance un-wrapper
32
+ async function deploy_access_control(a0, owner = a0, features = new BN(0)) {
33
+ const {proxy} = await deploy_erc1967_upgradeable_ac(a0, owner, features);
34
+ return proxy;
35
+ }
36
+
37
+ // RBAC proxy instance un-wrapper
38
+ async function deploy_access_control_v2(a0, owner = a0, features = new BN(0)) {
39
+ const {proxy} = await deploy_erc1967_upgradeable_ac(a0, owner, features);
40
+ if(owner !== ZERO_ADDRESS) {
41
+ const v2 = await deploy_upgradeable_ac_impl(a0, 2);
42
+ await proxy.upgradeTo(v2.address, {from: owner});
43
+ }
44
+ return proxy;
45
+ }
46
+
47
+ // run AccessControlUpgradeable (U-RBAC) tests
48
+ contract("AccessControlUpgradeable (U-RBAC) Core tests", function(accounts) {
49
+ // extract accounts to be used:
50
+ // A0 – special default zero account accounts[0] used by Truffle, reserved
51
+ // a0 – deployment account having all the permissions, reserved
52
+ // H0 – initial token holder account
53
+ // a1, a2,... – working accounts to perform tests on
54
+ const [A0, a0, H0, a1, a2, a3] = accounts;
55
+
56
+ // run the core RBACs behaviour test
57
+ behavesLikeRBAC(deploy_access_control, a0, a1, a2);
58
+ behavesLikeRBAC(deploy_access_control_v2, a0, a1, a2);
59
+ });
@@ -0,0 +1,59 @@
1
+ // AccessControlUpgradeable (U-RBAC) restrictedTo modifier tests
2
+
3
+ // Zeppelin test helpers
4
+ const {
5
+ BN,
6
+ constants,
7
+ expectEvent,
8
+ expectRevert,
9
+ } = require("@openzeppelin/test-helpers");
10
+ const {
11
+ assert,
12
+ expect,
13
+ } = require("chai");
14
+ const {
15
+ ZERO_ADDRESS,
16
+ ZERO_BYTES32,
17
+ MAX_UINT256,
18
+ } = constants;
19
+
20
+ // deployment routines in use
21
+ const {
22
+ deploy_erc1967_upgradeable_ac,
23
+ } = require("./include/deployment_routines");
24
+
25
+ // RBAC proxy instance un-wrapper
26
+ async function deploy_access_control(a0) {
27
+ const {proxy} = await deploy_erc1967_upgradeable_ac(a0);
28
+ return proxy;
29
+ }
30
+
31
+ // run AccessControlUpgradeable (U-RBAC) tests
32
+ contract('AccessControlUpgradeable (U-RBAC) "restrictedTo" Modifier tests', function(accounts) {
33
+ // extract accounts to be used:
34
+ // A0 – special default zero account accounts[0] used by Truffle, reserved
35
+ // a0 – deployment account having all the permissions, reserved
36
+ // H0 – initial token holder account
37
+ // a1, a2,... – working accounts to perform tests on
38
+ const [A0, a0, H0, a1, a2, a3] = accounts;
39
+
40
+ // `restrictedTo` modifier check
41
+ describe("restrictedTo modifier check", function() {
42
+ let access_control;
43
+ beforeEach(async function() {
44
+ access_control = await deploy_access_control(a0);
45
+ });
46
+ it("function protected with restrictedTo modifier fails when run not by an admin", async function() {
47
+ await expectRevert(access_control.restricted({from: a1}), "access denied");
48
+ });
49
+ describe("function protected with restrictedTo modifier succeeds when run by admin", async function() {
50
+ let receipt;
51
+ beforeEach(async function() {
52
+ receipt = await access_control.restricted({from: a0});
53
+ });
54
+ it('"Restricted" event is emitted', async function() {
55
+ expectEvent(receipt, "Restricted");
56
+ });
57
+ });
58
+ });
59
+ });
@@ -0,0 +1,120 @@
1
+ // UpgradeableAccessControl (U-RBAC) Core Tests
2
+
3
+ // Zeppelin test helpers
4
+ const {
5
+ BN,
6
+ constants,
7
+ expectEvent,
8
+ expectRevert,
9
+ } = require("@openzeppelin/test-helpers");
10
+ const {
11
+ assert,
12
+ expect,
13
+ } = require("chai");
14
+ const {
15
+ ZERO_ADDRESS,
16
+ ZERO_BYTES32,
17
+ MAX_UINT256,
18
+ } = constants;
19
+
20
+ // RBAC core features and roles
21
+ const {
22
+ not,
23
+ ROLE_UPGRADE_MANAGER,
24
+ } = require("./include/features_roles");
25
+
26
+ // deployment routines in use
27
+ const {
28
+ deploy_upgradeable_ac_impl,
29
+ deploy_erc1967_upgradeable_ac,
30
+ } = require("./include/deployment_routines");
31
+
32
+ // run UpgradeableAccessControl (U-RBAC) tests
33
+ contract("UpgradeableAccessControl (U-RBAC) Core tests", function(accounts) {
34
+ // extract accounts to be used:
35
+ // A0 – special default zero account accounts[0] used by Truffle, reserved
36
+ // a0 – deployment account having all the permissions, reserved
37
+ // H0 – initial token holder account
38
+ // a1, a2,... – working accounts to perform tests on
39
+ const [A0, a0, H0, a1, a2, a3] = accounts;
40
+
41
+ // define the "players"
42
+ const by = a1;
43
+ const to = a2;
44
+
45
+ // deploy the RBAC
46
+ let ac, impl1;
47
+ beforeEach(async function() {
48
+ ({proxy: ac, implementation: impl1} = await deploy_erc1967_upgradeable_ac(a0));
49
+ });
50
+
51
+ it("initialized version is 1", async function() {
52
+ expect(await ac.getInitializedVersion()).to.be.bignumber.that.equals("1");
53
+ });
54
+ it("it is impossible to re-initialize", async function() {
55
+ await expectRevert(ac.postConstruct(ZERO_ADDRESS, 0, {from: a0}), "Initializable: contract is already initialized");
56
+ });
57
+ describe("when there is new (v2) implementation available", function() {
58
+ let impl2;
59
+ beforeEach(async function() {
60
+ impl2 = await deploy_upgradeable_ac_impl(a0, 2);
61
+ });
62
+ describe("when performed by UPGRADE_MANAGER", function() {
63
+ beforeEach(async function() {
64
+ await ac.updateRole(by, ROLE_UPGRADE_MANAGER, {from: a0});
65
+ });
66
+ it("implementation upgrade with initialization fails (already initialized)", async function() {
67
+ // prepare the initialization call bytes
68
+ const init_data = ac.contract.methods.postConstruct(ZERO_ADDRESS, 0).encodeABI();
69
+
70
+ // and upgrade the implementation
71
+ await expectRevert(
72
+ ac.upgradeToAndCall(impl2.address, init_data, {from: by}),
73
+ "Initializable: contract is already initialized"
74
+ );
75
+ });
76
+ describe("implementation upgrade without initialization succeeds", function() {
77
+ let receipt;
78
+ beforeEach(async function() {
79
+ receipt = await ac.upgradeTo(impl2.address, {from: by});
80
+ });
81
+ it('"Upgraded" event is emitted', async function() {
82
+ expectEvent(receipt, "Upgraded", {implementation: impl2.address});
83
+ });
84
+ it("implementation address is set as expected", async function() {
85
+ expect(await ac.getImplementation()).to.be.equal(impl2.address);
86
+ });
87
+ });
88
+ it("direct initialization of the implementation (bypassing proxy) fails", async function() {
89
+ await expectRevert(impl1.postConstruct(ZERO_ADDRESS, 0, {from: by}), "Initializable: contract is already initialized");
90
+ });
91
+ it("direct upgrade of the implementation (bypassing proxy) fails", async function() {
92
+ await expectRevert(impl1.upgradeTo(impl2.address, {from: by}), "Function must be called through delegatecall");
93
+ });
94
+ });
95
+ describe("otherwise (no UPGRADE_MANAGER permission)", function() {
96
+ beforeEach(async function() {
97
+ await ac.updateRole(by, not(ROLE_UPGRADE_MANAGER), {from: a0});
98
+ });
99
+ it("implementation upgrade with initialization fails (already initialized)", async function() {
100
+ // prepare the initialization call bytes
101
+ const init_data = ac.contract.methods.postConstruct(ZERO_ADDRESS, 0).encodeABI();
102
+
103
+ // and upgrade the implementation
104
+ await expectRevert(
105
+ ac.upgradeToAndCall(impl2.address, init_data, {from: by}),
106
+ "access denied"
107
+ );
108
+ });
109
+ it("implementation upgrade without initialization reverts", async function() {
110
+ await expectRevert(ac.upgradeTo(impl2.address, {from: by}), "access denied");
111
+ });
112
+ it("direct initialization of the implementation (bypassing proxy) fails", async function() {
113
+ await expectRevert(impl1.postConstruct(ZERO_ADDRESS, 0, {from: by}), "Initializable: contract is already initialized");
114
+ });
115
+ it("direct upgrade of the implementation (bypassing proxy) fails", async function() {
116
+ await expectRevert(impl1.upgradeTo(impl2.address, {from: by}), "Function must be called through delegatecall");
117
+ });
118
+ });
119
+ });
120
+ });