Skip to content

Commit 3e93a14

Browse files
authored
Merge pull request #34 from dl-solidity-library/dev
Dev
2 parents e14b79f + 6e021ac commit 3e93a14

File tree

25 files changed

+1992
-108
lines changed

25 files changed

+1992
-108
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88
**Elaborate solidity development modules library by DL.**
99

10-
The library consist of modules and utilities that are built on top of [Openzeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) but goes far beyond the mediocre solidity.
10+
The library consists of modules and utilities that are built on top of [Openzeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) but go far beyond mediocre solidity.
1111

1212
- Implementation of [**Contracts Registry**](https://eips.ethereum.org/EIPS/eip-6224) pattern
1313
- Versatile **RBAC** smart contract
1414
- Enhanced and simplified [**Diamond**](https://eips.ethereum.org/EIPS/eip-2535) pattern
15-
- Utilities to ease work with ERC20 decimals, arrays and sets
15+
- Heap based priority queue library
16+
- Utilities to ease work with ERC20 decimals, arrays, and sets
1617

1718
## Overview
1819

@@ -26,7 +27,7 @@ The latest stable version is always in the `master` branch.
2627

2728
## Usage
2829

29-
You will find the smart contracts in the `/contracts` directory. Fell free to play around and check the source code, it is rather descriptive.
30+
You will find the smart contracts in the `/contracts` directory. Feel free to play around and check the source code, it is rather descriptive.
3031

3132
Once the [npm package](https://www.npmjs.com/package/@dlsl/dev-modules) is installed, one can use the modules just like that:
3233

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
5+
6+
/**
7+
* @notice The Whitelist Access Control module
8+
*
9+
* This is a simple abstract contract that implements whitelisting logic.
10+
*
11+
* The contract is based on a Merkle tree, which allows for the huge whitelists to be cheaply validated courtesy of
12+
* O(log(n)) tree complexity. The whitelist itself is stored in the tree leaves and only the root of the tree is saved on-chain.
13+
*
14+
* To validate the whitelist belonging, the tree leaf (the whitelist element) has to be computed and passed to the
15+
* "root-construction" function together with the corresponding tree branches. The function will then check the
16+
* roots equality. If the roots match, the element belongs to the whitelist.
17+
*
18+
* Note: the branch nodes are sorted numerically.
19+
*/
20+
abstract contract MerkleWhitelisted {
21+
using MerkleProof for bytes32[];
22+
23+
bytes32 private _merkleRoot;
24+
25+
modifier onlyWhitelisted(bytes memory data_, bytes32[] calldata merkleProof_) {
26+
require(
27+
isWhitelisted(keccak256(data_), merkleProof_),
28+
"MerkleWhitelisted: not whitelisted"
29+
);
30+
_;
31+
}
32+
33+
modifier onlyWhitelistedUser(address user_, bytes32[] calldata merkleProof_) {
34+
require(isWhitelistedUser(user_, merkleProof_), "MerkleWhitelisted: not whitelisted");
35+
_;
36+
}
37+
38+
/**
39+
* @notice The function to check if the leaf belongs to the Merkle tree
40+
* @param leaf_ the leaf to be checked
41+
* @param merkleProof_ the path from the leaf to the Merkle tree root
42+
* @return true if the leaf belongs to the Merkle tree, false otherwise
43+
*/
44+
function isWhitelisted(
45+
bytes32 leaf_,
46+
bytes32[] calldata merkleProof_
47+
) public view returns (bool) {
48+
return merkleProof_.verifyCalldata(_merkleRoot, leaf_);
49+
}
50+
51+
/**
52+
* @notice The function to check if the user belongs to the Merkle tree
53+
* @param user_ the user to be checked
54+
* @param merkleProof_ the path from the user to the Merkle tree root
55+
* @return true if the user belongs to the Merkle tree, false otherwise
56+
*/
57+
function isWhitelistedUser(
58+
address user_,
59+
bytes32[] calldata merkleProof_
60+
) public view returns (bool) {
61+
return isWhitelisted(keccak256(abi.encodePacked(user_)), merkleProof_);
62+
}
63+
64+
/**
65+
* @notice The function to get the current Merkle root
66+
* @return the current Merkle root or zero bytes if it has not been set
67+
*/
68+
function getMerkleRoot() public view returns (bytes32) {
69+
return _merkleRoot;
70+
}
71+
72+
/**
73+
* @notice The function that should be called from the child contract to set the Merkle root
74+
* @param merkleRoot_ the Merkle root to be set
75+
*/
76+
function _setMerkleRoot(bytes32 merkleRoot_) internal {
77+
_merkleRoot = merkleRoot_;
78+
}
79+
}

contracts/access-control/RBAC.sol

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ abstract contract RBAC is IRBAC, Initializable {
133133
/**
134134
* @notice The function to get the list of user roles
135135
* @param who_ the user
136-
* @return roles_ the roes of the user
136+
* @return roles_ the roles of the user
137137
*/
138138
function getUserRoles(address who_) public view override returns (string[] memory roles_) {
139139
return _userRoles[who_].values();
@@ -181,51 +181,30 @@ abstract contract RBAC is IRBAC, Initializable {
181181
}
182182

183183
/**
184-
* @notice The function to check the user's possesion of the role
184+
* @dev DO NOT call `super.hasPermission(...)` in derived contracts, because this method
185+
* handles not 2 but 3 states: NO PERMISSION, ALLOWED, DISALLOWED
186+
* @notice The function to check the user's possession of the role
185187
* @param who_ the user
186188
* @param resource_ the resource the user has to have the permission of
187189
* @param permission_ the permission the user has to have
188-
* @return true_ if user has the permission, false otherwise
190+
* @return isAllowed_ true if the user has the permission, false otherwise
189191
*/
190192
function hasPermission(
191193
address who_,
192194
string memory resource_,
193195
string memory permission_
194-
) public view override returns (bool) {
195-
StringSet.Set storage _roles = _userRoles[who_];
196+
) public view virtual override returns (bool isAllowed_) {
197+
string[] memory roles_ = getUserRoles(who_);
196198

197-
uint256 length_ = _roles.length();
198-
bool isAllowed_;
199+
for (uint256 i = 0; i < roles_.length; i++) {
200+
string memory role_ = roles_[i];
199201

200-
for (uint256 i = 0; i < length_; i++) {
201-
string memory role_ = _roles.at(i);
202-
203-
StringSet.Set storage _allDisallowed = _rolePermissions[role_][false][ALL_RESOURCE];
204-
StringSet.Set storage _allAllowed = _rolePermissions[role_][true][ALL_RESOURCE];
205-
206-
StringSet.Set storage _disallowed = _rolePermissions[role_][false][resource_];
207-
StringSet.Set storage _allowed = _rolePermissions[role_][true][resource_];
208-
209-
if (
210-
_allDisallowed.contains(ALL_PERMISSION) ||
211-
_allDisallowed.contains(permission_) ||
212-
_disallowed.contains(ALL_PERMISSION) ||
213-
_disallowed.contains(permission_)
214-
) {
202+
if (_isDisallowed(role_, resource_, permission_)) {
215203
return false;
216204
}
217205

218-
if (
219-
_allAllowed.contains(ALL_PERMISSION) ||
220-
_allAllowed.contains(permission_) ||
221-
_allowed.contains(ALL_PERMISSION) ||
222-
_allowed.contains(permission_)
223-
) {
224-
isAllowed_ = true;
225-
}
206+
isAllowed_ = isAllowed_ || _isAllowed(role_, resource_, permission_);
226207
}
227-
228-
return isAllowed_;
229208
}
230209

231210
/**
@@ -296,4 +275,50 @@ abstract contract RBAC is IRBAC, Initializable {
296275

297276
emit RemovedPermissions(role_, resourceToRemove_, permissionsToRemove_, allowed_);
298277
}
278+
279+
/**
280+
* @notice The function to check if the role has the permission
281+
* @param role_ the role to search the permission in
282+
* @param resource_ the role resource to search the permission in
283+
* @param permission_ the permission to search
284+
* @return true_ if the role has the permission, false otherwise
285+
*/
286+
function _isAllowed(
287+
string memory role_,
288+
string memory resource_,
289+
string memory permission_
290+
) internal view returns (bool) {
291+
mapping(string => StringSet.Set) storage _resources = _rolePermissions[role_][true];
292+
293+
StringSet.Set storage _allAllowed = _resources[ALL_RESOURCE];
294+
StringSet.Set storage _allowed = _resources[resource_];
295+
296+
return (_allAllowed.contains(ALL_PERMISSION) ||
297+
_allAllowed.contains(permission_) ||
298+
_allowed.contains(ALL_PERMISSION) ||
299+
_allowed.contains(permission_));
300+
}
301+
302+
/**
303+
* @notice The function to check if the role has the antipermission
304+
* @param role_ the role to search the antipermission in
305+
* @param resource_ the role resource to search the antipermission in
306+
* @param permission_ the antipermission to search
307+
* @return true_ if the role has the antipermission, false otherwise
308+
*/
309+
function _isDisallowed(
310+
string memory role_,
311+
string memory resource_,
312+
string memory permission_
313+
) internal view returns (bool) {
314+
mapping(string => StringSet.Set) storage _resources = _rolePermissions[role_][false];
315+
316+
StringSet.Set storage _allDisallowed = _resources[ALL_RESOURCE];
317+
StringSet.Set storage _disallowed = _resources[resource_];
318+
319+
return (_allDisallowed.contains(ALL_PERMISSION) ||
320+
_allDisallowed.contains(permission_) ||
321+
_disallowed.contains(ALL_PERMISSION) ||
322+
_disallowed.contains(permission_));
323+
}
299324
}

0 commit comments

Comments
 (0)