@lazy-sol/access-control 1.0.6 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +43 -0
- package/README.md +69 -48
- package/artifacts/contracts/OwnableToAccessControlAdapter.sol/OwnableToAccessControlAdapter.json +7 -64
- package/contracts/AccessControl.sol +15 -219
- package/contracts/AccessControlCore.sol +353 -0
- package/contracts/AdapterFactory.sol +1 -1
- package/contracts/OwnableToAccessControlAdapter.sol +7 -5
- package/contracts/mocks/AccessControlMock.sol +7 -1
- package/hardhat.config.js +6 -6
- package/index.js +4 -0
- package/package.json +12 -10
- package/test/include/deployment_routines.js +3 -12
- package/test/include/rbac.behaviour.js +22 -7
- package/test/ownable_to_rbac_adapter.js +1 -1
- package/test/ownable_to_rbac_adapter_rbac.js +3 -3
- package/test/rbac_modifier.js +1 -1
- package/ui.html +506 -0
@@ -1,5 +1,5 @@
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
2
|
-
pragma solidity >=0.4
|
2
|
+
pragma solidity >=0.8.4;
|
3
3
|
|
4
4
|
import "../AccessControl.sol";
|
5
5
|
|
@@ -11,4 +11,10 @@ contract AccessControlMock is AccessControl {
|
|
11
11
|
function restricted() public restrictedTo(RESTRICTED_ROLE) {
|
12
12
|
emit Restricted();
|
13
13
|
}
|
14
|
+
function requireSenderInRole(uint256 required) public view {
|
15
|
+
_requireSenderInRole(required);
|
16
|
+
}
|
17
|
+
function requireAccessCondition(bool condition) public pure {
|
18
|
+
_requireAccessCondition(condition);
|
19
|
+
}
|
14
20
|
}
|
package/hardhat.config.js
CHANGED
@@ -453,12 +453,12 @@ function get_endpoint_url(network_name) {
|
|
453
453
|
// create a key: https://www.alchemy.com/
|
454
454
|
if(process.env.ALCHEMY_KEY) {
|
455
455
|
switch(network_name) {
|
456
|
-
case "mainnet": return "https://eth-mainnet.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
457
|
-
case "ropsten": return "https://eth-ropsten.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
458
|
-
case "rinkeby": return "https://eth-rinkeby.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
459
|
-
case "kovan": return "https://eth-kovan.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
460
|
-
case "goerli": return "https://eth-goerli.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
461
|
-
case "sepolia": return "https://eth-sepolia.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
456
|
+
case "mainnet": return "https://eth-mainnet.g.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
457
|
+
case "ropsten": return "https://eth-ropsten.g.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
458
|
+
case "rinkeby": return "https://eth-rinkeby.g.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
459
|
+
case "kovan": return "https://eth-kovan.g.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
460
|
+
case "goerli": return "https://eth-goerli.g.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
461
|
+
case "sepolia": return "https://eth-sepolia.g.alchemyapi.io/v2/" + process.env.ALCHEMY_KEY;
|
462
462
|
case "polygon": return "https://polygon-mainnet.g.alchemy.com/v2/" + process.env.ALCHEMY_KEY;
|
463
463
|
case "mumbai": return "https://polygon-mumbai.g.alchemy.com/v2/" + process.env.ALCHEMY_KEY;
|
464
464
|
case "base_mainnet": return "https://base-mainnet.g.alchemy.com/v2/" + process.env.ALCHEMY_KEY;
|
package/index.js
CHANGED
@@ -9,6 +9,9 @@ const {
|
|
9
9
|
not,
|
10
10
|
} = require("./scripts/include/features_roles");
|
11
11
|
|
12
|
+
// RBAC behaviours
|
13
|
+
const {behavesLikeRBAC} = require("./test/include/rbac.behaviour");
|
14
|
+
|
12
15
|
// Re-export the functions
|
13
16
|
module.exports = {
|
14
17
|
ROLE_ACCESS_MANAGER,
|
@@ -16,4 +19,5 @@ module.exports = {
|
|
16
19
|
FULL_PRIVILEGES_MASK,
|
17
20
|
or,
|
18
21
|
not,
|
22
|
+
behavesLikeRBAC,
|
19
23
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lazy-sol/access-control",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.1.1",
|
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": {
|
@@ -21,19 +21,21 @@
|
|
21
21
|
],
|
22
22
|
"author": "Basil Gorin",
|
23
23
|
"license": "MIT",
|
24
|
-
"dependencies": {
|
25
|
-
"@lazy-sol/a-missing-gem": "^1.0.9",
|
26
|
-
"@nomiclabs/hardhat-truffle5": "^2.0.7",
|
27
|
-
"hardhat": "^2.22.6",
|
28
|
-
"hardhat-deploy": "^0.11.45"
|
29
|
-
},
|
30
24
|
"devDependencies": {
|
31
|
-
"@lazy-sol/
|
25
|
+
"@lazy-sol/a-missing-gem": "^1.0.10",
|
26
|
+
"@lazy-sol/zeppelin-test-helpers": "^1.0.5",
|
27
|
+
"@nomiclabs/hardhat-truffle5": "^2.0.7",
|
28
|
+
"hardhat": "^2.22.13",
|
29
|
+
"hardhat-deploy": "^0.11.45",
|
32
30
|
"hardhat-gas-reporter": "^1.0.10",
|
33
|
-
"solidity-coverage": "^0.8.
|
31
|
+
"solidity-coverage": "^0.8.13"
|
34
32
|
},
|
35
33
|
"overrides": {
|
34
|
+
"axios": ">=1.7.5",
|
35
|
+
"micromatch": "^4.0.8",
|
36
|
+
"tar": "^6.2.1",
|
36
37
|
"tough-cookie": "^4.1.3",
|
37
|
-
"yargs-parser": "^5.0.1"
|
38
|
+
"yargs-parser": "^5.0.1",
|
39
|
+
"ws": "^8.0.0"
|
38
40
|
}
|
39
41
|
}
|
@@ -3,23 +3,14 @@
|
|
3
3
|
* (doesn't return any value on successful operation)
|
4
4
|
*
|
5
5
|
* @param a0 smart contract owner
|
6
|
-
* @param H0 initial token holder address
|
7
6
|
* @returns USDT ERC20 instance
|
8
7
|
*/
|
9
|
-
async function deploy_usdt(a0
|
8
|
+
async function deploy_usdt(a0) {
|
10
9
|
// smart contracts required
|
11
10
|
const USDTContract = artifacts.require("TetherToken");
|
12
11
|
|
13
|
-
// deploy the token
|
14
|
-
|
15
|
-
|
16
|
-
// move the initial supply if required
|
17
|
-
if(H0 !== a0) {
|
18
|
-
await token.transfer(H0, S0, {from: a0});
|
19
|
-
}
|
20
|
-
|
21
|
-
// return the reference
|
22
|
-
return token;
|
12
|
+
// deploy the token and return the reference
|
13
|
+
return await USDTContract.new(0, "Tether USD", "USDT", 6, {from: a0});
|
23
14
|
}
|
24
15
|
|
25
16
|
/**
|
@@ -15,16 +15,17 @@ const {
|
|
15
15
|
MAX_UINT256,
|
16
16
|
} = constants;
|
17
17
|
|
18
|
-
// BN
|
18
|
+
// BN constants and utilities
|
19
19
|
const {
|
20
20
|
random_bn255,
|
21
21
|
random_bn256,
|
22
|
-
} = require("@lazy-sol/a-missing-gem
|
22
|
+
} = require("@lazy-sol/a-missing-gem");
|
23
23
|
|
24
24
|
// RBAC core features and roles
|
25
25
|
const {
|
26
26
|
not,
|
27
|
-
ROLE_ACCESS_MANAGER,
|
27
|
+
ROLE_ACCESS_MANAGER,
|
28
|
+
FULL_PRIVILEGES_MASK,
|
28
29
|
} = require("../../scripts/include/features_roles");
|
29
30
|
|
30
31
|
/**
|
@@ -40,6 +41,18 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
40
41
|
const by = a1;
|
41
42
|
const to = a2;
|
42
43
|
|
44
|
+
describe("requireAccessCondition(condition) pure function", function() {
|
45
|
+
let access_control;
|
46
|
+
beforeEach(async function() {
|
47
|
+
access_control = await deployment_fn.call(this, a0, ZERO_ADDRESS, 0);
|
48
|
+
});
|
49
|
+
it("throws if condition is false", async function() {
|
50
|
+
await expectRevert(access_control.requireAccessCondition(false, {from: a0}), "AccessDenied()");
|
51
|
+
});
|
52
|
+
it("succeeds if condition is true", async function() {
|
53
|
+
await access_control.requireAccessCondition(true, {from: a0});
|
54
|
+
});
|
55
|
+
});
|
43
56
|
describe("deployment and initial state", function() {
|
44
57
|
function deploy_and_check(owner, features) {
|
45
58
|
let access_control;
|
@@ -81,7 +94,7 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
81
94
|
deploy_and_check(a1, random_bn256());
|
82
95
|
});
|
83
96
|
});
|
84
|
-
describe("when deployed with
|
97
|
+
describe("when deployed with no initial features", function() {
|
85
98
|
let access_control;
|
86
99
|
beforeEach(async function() {
|
87
100
|
access_control = await deployment_fn.call(this, a0);
|
@@ -92,7 +105,7 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
92
105
|
beforeEach(async function() {
|
93
106
|
await access_control.updateRole(by, ROLE_ACCESS_MANAGER, {from: a0});
|
94
107
|
});
|
95
|
-
describe("when ACCESS_MANAGER has full set of permissions", function() {
|
108
|
+
describe("when ACCESS_MANAGER has the full set of permissions", function() {
|
96
109
|
beforeEach(async function() {
|
97
110
|
await access_control.updateRole(by, MAX_UINT256, {from: a0});
|
98
111
|
});
|
@@ -271,6 +284,7 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
271
284
|
it("operator becomes an ACCESS_MANAGER", async function() {
|
272
285
|
expect(await access_control.isOperatorInRole(to, ROLE_ACCESS_MANAGER), "operator").to.be.true;
|
273
286
|
expect(await access_control.isSenderInRole(ROLE_ACCESS_MANAGER, {from: to}), "sender").to.be.true;
|
287
|
+
await access_control.requireSenderInRole(ROLE_ACCESS_MANAGER, {from: to});
|
274
288
|
});
|
275
289
|
});
|
276
290
|
describe("when ACCESS_MANAGER revokes ACCESS_MANAGER permission from itself", function() {
|
@@ -280,15 +294,16 @@ function behavesLikeRBAC(deployment_fn, a0, a1, a2) {
|
|
280
294
|
it("operator ceases to be an ACCESS_MANAGER", async function() {
|
281
295
|
expect(await access_control.isOperatorInRole(by, ROLE_ACCESS_MANAGER), "operator").to.be.false;
|
282
296
|
expect(await access_control.isSenderInRole(ROLE_ACCESS_MANAGER, {from: by}), "sender").to.be.false;
|
297
|
+
await expectRevert(access_control.requireSenderInRole(ROLE_ACCESS_MANAGER, {from: to}), "AccessDenied()");
|
283
298
|
});
|
284
299
|
});
|
285
300
|
});
|
286
301
|
describe("otherwise (no ACCESS_MANAGER permission)", function() {
|
287
302
|
it("updateFeatures reverts", async function() {
|
288
|
-
await expectRevert(access_control.updateFeatures(1, {from: by}), "
|
303
|
+
await expectRevert(access_control.updateFeatures(1, {from: by}), "AccessDenied()");
|
289
304
|
});
|
290
305
|
it("updateRole reverts", async function() {
|
291
|
-
await expectRevert(access_control.updateRole(to, 1, {from: by}), "
|
306
|
+
await expectRevert(access_control.updateRole(to, 1, {from: by}), "AccessDenied()");
|
292
307
|
});
|
293
308
|
});
|
294
309
|
}
|
@@ -72,7 +72,7 @@ contract("OwnableToAccessControlAdapter tests", function(accounts) {
|
|
72
72
|
from: a1,
|
73
73
|
to: adapter.address,
|
74
74
|
data: target.contract.methods.transferOwnership(a2).encodeABI(),
|
75
|
-
}), "
|
75
|
+
}), "AccessDenied()");
|
76
76
|
});
|
77
77
|
describe("once an account has authorization to execute transferOwnership function", function() {
|
78
78
|
beforeEach(async function() {
|
@@ -43,12 +43,12 @@ contract("OwnableToAccessControlAdapter: RBAC tests", function(accounts) {
|
|
43
43
|
({target, adapter} = await deploy_ownable_to_ac_adapter(a0));
|
44
44
|
});
|
45
45
|
|
46
|
-
it("it is impossible to configure the access role from unauthorized account", async function() {
|
46
|
+
it("it is impossible to configure the access role from an unauthorized account", async function() {
|
47
47
|
const operator = a1;
|
48
48
|
await adapter.updateRole(operator, not(ROLE_ACCESS_ROLES_MANAGER), {from: a0});
|
49
|
-
await expectRevert(adapter.updateAccessRole("1", 1, {from: operator}), "
|
49
|
+
await expectRevert(adapter.updateAccessRole("1", 1, {from: operator}), "AccessDenied()");
|
50
50
|
});
|
51
|
-
it("it is possible to configure the access role from authorized account", async function() {
|
51
|
+
it("it is possible to configure the access role from the authorized account", async function() {
|
52
52
|
const operator = a1;
|
53
53
|
await adapter.updateRole(operator, ROLE_ACCESS_ROLES_MANAGER, {from: a0});
|
54
54
|
adapter.updateAccessRole("1", 1, {from: operator});
|
package/test/rbac_modifier.js
CHANGED
@@ -38,7 +38,7 @@ contract('AccessControl (RBAC) "restrictedTo" Modifier tests', function(accounts
|
|
38
38
|
access_control = await deploy_access_control(a0);
|
39
39
|
});
|
40
40
|
it("function protected with restrictedTo modifier fails when run not by an admin", async function() {
|
41
|
-
await expectRevert(access_control.restricted({from: a1}), "
|
41
|
+
await expectRevert(access_control.restricted({from: a1}), "AccessDenied()");
|
42
42
|
});
|
43
43
|
describe("function protected with restrictedTo modifier succeeds when run by admin", async function() {
|
44
44
|
let receipt;
|