Skip to content

Commit 0f753aa

Browse files
committed
Add AccessManagerLight
1 parent a2a4788 commit 0f753aa

File tree

6 files changed

+175
-2
lines changed

6 files changed

+175
-2
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol";
6+
import { Masks } from "../utils/Masks.sol";
7+
8+
contract AccessManagerLight is IAuthority {
9+
using Masks for *;
10+
11+
uint8 public constant ADMIN = 0x00;
12+
uint8 public constant PUBLIC = 0xFF;
13+
Masks.Mask public immutable ADMIN_MASK = ADMIN.toMask();
14+
Masks.Mask public immutable PUBLIC_MASK = PUBLIC.toMask();
15+
16+
mapping(address => Masks.Mask ) private _permissions;
17+
mapping(address => mapping(bytes4 => Masks.Mask)) private _restrictions;
18+
mapping(uint8 => Masks.Mask ) private _admin;
19+
20+
event GroupAdded(address indexed user, uint8 indexed group);
21+
event GroupRemoved(address indexed user, uint8 indexed group);
22+
event GroupAdmins(uint8 indexed group, Masks.Mask admins);
23+
event Requirements(address indexed target, bytes4 indexed selector, Masks.Mask groups);
24+
25+
error MissingPermissions(address user, Masks.Mask permissions, Masks.Mask restriction);
26+
27+
modifier onlyRole(Masks.Mask restriction) {
28+
Masks.Mask permissions = getGroups(msg.sender);
29+
if (permissions.intersection(restriction).isEmpty()) {
30+
revert MissingPermissions(msg.sender, permissions, restriction);
31+
}
32+
_;
33+
}
34+
35+
constructor(address admin) {
36+
_addGroup(admin, 0);
37+
}
38+
39+
// Getters
40+
function canCall(address caller, address target, bytes4 selector) public view returns (bool) {
41+
return !getGroups(caller).intersection(getRequirements(target, selector)).isEmpty();
42+
}
43+
44+
function getGroups(address user) public view returns (Masks.Mask) {
45+
return _permissions[user].union(PUBLIC_MASK);
46+
}
47+
48+
function getGroupAdmins(uint8 group) public view returns (Masks.Mask) {
49+
return _admin[group].union(ADMIN_MASK); // Admin have power over all groups
50+
}
51+
52+
function getRequirements(address target, bytes4 selector) public view returns (Masks.Mask) {
53+
return _restrictions[target][selector].union(ADMIN_MASK); // Admins can call an function
54+
}
55+
56+
// Group management
57+
function addGroup(address user, uint8 group) public onlyRole(getGroupAdmins(group)) {
58+
_addGroup(user, group);
59+
}
60+
61+
function remGroup(address user, uint8 group) public onlyRole(getGroupAdmins(group)) {
62+
_remGroup(user, group);
63+
}
64+
65+
function _addGroup(address user, uint8 group) internal {
66+
_permissions[user] = _permissions[user].union(group.toMask());
67+
emit GroupAdded(user, group);
68+
}
69+
70+
function _remGroup(address user, uint8 group) internal {
71+
_permissions[user] = _permissions[user].difference(group.toMask());
72+
emit GroupRemoved(user, group);
73+
}
74+
75+
// Group admin management
76+
function setGroupAdmins(uint8 group, uint8[] calldata admins) public onlyRole(ADMIN_MASK) {
77+
_setGroupAdmins(group, admins.toMask());
78+
}
79+
80+
function _setGroupAdmins(uint8 group, Masks.Mask admins) internal {
81+
_admin[group] = admins;
82+
emit GroupAdmins(group, admins);
83+
}
84+
85+
// Requirement management
86+
function setRequirements(address target, bytes4[] calldata selectors, uint8[] calldata groups) public onlyRole(ADMIN_MASK) {
87+
Masks.Mask mask = groups.toMask();
88+
for (uint256 i = 0; i < selectors.length; ++i) {
89+
_setRequirements(target, selectors[i], mask);
90+
}
91+
}
92+
93+
function _setRequirements(address target, bytes4 selector, Masks.Mask groups) internal {
94+
_restrictions[target][selector] = groups;
95+
emit Requirements(target, selector, groups);
96+
}
97+
}

contracts/utils/Masks.sol

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
library Masks {
6+
using Masks for *;
7+
8+
type Mask is bytes32;
9+
10+
function toMask(uint8 group) internal pure returns (Mask) {
11+
return Mask.wrap(bytes32(1 << group));
12+
}
13+
14+
function toMask(uint8[] memory groups) internal pure returns (Mask) {
15+
Masks.Mask set = Mask.wrap(0);
16+
for (uint256 i = 0; i < groups.length; ++i) {
17+
set = set.union(groups[i].toMask());
18+
}
19+
return set;
20+
}
21+
22+
function get(Mask self, uint8 group) internal pure returns (bool) {
23+
return !group.toMask().intersection(self).isEmpty();
24+
}
25+
26+
function isEmpty(Mask self) internal pure returns (bool) {
27+
return Mask.unwrap(self) == bytes32(0);
28+
}
29+
30+
function complement(Mask m1) internal pure returns (Mask) {
31+
return Mask.wrap(~Mask.unwrap(m1));
32+
}
33+
34+
function union(Mask m1, Mask m2) internal pure returns (Mask) {
35+
return Mask.wrap(Mask.unwrap(m1) | Mask.unwrap(m2));
36+
}
37+
38+
function intersection(Mask m1, Mask m2) internal pure returns (Mask) {
39+
return Mask.wrap(Mask.unwrap(m1) & Mask.unwrap(m2));
40+
}
41+
42+
function difference(Mask m1, Mask m2) internal pure returns (Mask) {
43+
return m1.intersection(m2.complement());
44+
}
45+
46+
function symetric_difference(Mask m1, Mask m2) internal pure returns (Mask) {
47+
return m1.union(m2).difference(m1.intersection(m2));
48+
}
49+
}

hardhat/remappings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ task(TASK_COMPILE_GET_REMAPPINGS).setAction((taskArgs, env, runSuper) =>
1111
.readFileSync('remappings.txt', 'utf-8')
1212
.split('\n')
1313
.filter(Boolean)
14+
.filter(line => !line.startsWith("#"))
1415
.map(line => line.trim().split('=')),
1516
),
1617
),

package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,9 @@
3535
"ethers": "^6.12.1",
3636
"hardhat": "^2.22.3",
3737
"yargs": "^17.7.2"
38+
},
39+
"dependencies": {
40+
"@openzeppelin/contracts": "^5.0.2",
41+
"@openzeppelin/contracts-upgradeable": "^5.0.2"
3842
}
3943
}

remappings.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
@openzeppelin/contracts/=lib/@openzeppelin-contracts/contracts/
2-
@openzeppelin/contracts-upgradeable/=lib/@openzeppelin-contracts-upgradeable/contracts/
1+
# v5 (released)
2+
@openzeppelin/contracts@v5/=@openzeppelin/contracts/
3+
@openzeppelin/contracts-upgradeable@v5/=@openzeppelin/contracts-upgradeable/
4+
5+
# Master (unreleased)
6+
@openzeppelin/contracts@master/=lib/@openzeppelin-contracts/contracts/
7+
@openzeppelin/contracts-upgradeable@master/=lib/@openzeppelin-contracts-upgradeable/contracts/

0 commit comments

Comments
 (0)