@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/.editorconfig +21 -0
- package/.solcover.js +39 -0
- package/CRIBBED_CODE.txt +26 -0
- package/LICENSE.txt +22 -0
- package/README.md +18 -0
- package/contracts/InitializableAccessControl.sol +360 -0
- package/contracts/UpgradeableAccessControl.sol +80 -0
- package/contracts/mocks/UpgradeableAccessControlMocks.sol +31 -0
- package/hardhat.config.js +82 -0
- package/package.json +42 -0
- package/test/include/deployment_routines.js +51 -0
- package/test/include/features_roles.js +41 -0
- package/test/include/rbac.behaviour.js +315 -0
- package/test/rbac_core.js +59 -0
- package/test/rbac_modifier.js +59 -0
- package/test/rbac_upgradeable.js +120 -0
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
|
+
});
|