@lazy-sol/access-control-upgradeable 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ });