Skip to content

Commit

Permalink
feat: diamond erc20 with Nayms governance and access control systems …
Browse files Browse the repository at this point in the history
…[wip]
  • Loading branch information
kevin-fruitful committed Mar 13, 2024
1 parent c0e6781 commit aa97324
Show file tree
Hide file tree
Showing 20 changed files with 1,631 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/diamond-2-hardhat"]
path = lib/diamond-2-hardhat
url = https://github.com/mudgen/diamond-2-hardhat
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/Vectorized/solady
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"editor.formatOnSave": true,
"solidity.formatter": "forge",
"[solidity]": {
"editor.defaultFormatter": "NomicFoundation.hardhat-solidity"
}
}
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at ec85d4
8 changes: 8 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
forge-std/=lib/forge-std/src/
openzeppelin/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
diamond-2-hardhat/=lib/diamond-2-hardhat/contracts/
ds-test/=lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
openzeppelin-contracts/=lib/openzeppelin-contracts/
solady/=lib/solady/src/
196 changes: 196 additions & 0 deletions src/facets/ACLFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import { LibACL, LibHelpers } from "../libs/LibACL.sol";
import { LibConstants as LC } from "../libs/LibConstants.sol";
import { Modifiers } from "../shared/Modifiers.sol";
import { AssignerCannotUnassignRole } from "../shared/CustomErrors.sol";

/**
* @title Access Control List
* @notice Use it to authorize various actions on the contracts
* @dev Use it to (un)assign or check role membership
*/
contract ACLFacet is Modifiers {
using LibHelpers for *;

/**
* @notice Assign a `_roleId` to the object in given context
* @dev Any object ID can be a context, system is a special context with highest priority
* @param _objectId ID of an object that is being assigned a role
* @param _contextId ID of the context in which a role is being assigned
* @param _role Name of the role being assigned
*/
function assignRole(bytes32 _objectId, bytes32 _contextId, string memory _role) external {
bytes32 assignerId = LibHelpers._getIdForAddress(msg.sender);
require(
LibACL._canAssign(assignerId, _objectId, _contextId, LibHelpers._stringToBytes32(_role)),
"not in assigners group"
);

/// @dev First, assigner attempts to unassign the role.
bytes32 roleId = LibACL._getRoleInContext(_objectId, _contextId);
if (roleId != 0 && !LibACL._canAssign(assignerId, _objectId, _contextId, roleId)) {
revert AssignerCannotUnassignRole(assignerId, _objectId, _contextId, string(roleId._bytes32ToBytes()));
}
LibACL._unassignRole(_objectId, _contextId);

/// @dev Second, assign the role.
LibACL._assignRole(_objectId, _contextId, LibHelpers._stringToBytes32(_role));
}

/**
* @notice Unassign object from a role in given context
* @dev Any object ID can be a context, system is a special context with highest priority
* @param _objectId ID of an object that is being unassigned from a role
* @param _contextId ID of the context in which a role membership is being revoked
*/
function unassignRole(bytes32 _objectId, bytes32 _contextId) external {
bytes32 roleId = LibACL._getRoleInContext(_objectId, _contextId);
bytes32 assignerId = LibHelpers._getIdForAddress(msg.sender);
require(LibACL._canAssign(assignerId, _objectId, _contextId, roleId), "not in assigners group");
LibACL._unassignRole(_objectId, _contextId);
}

/**
* @notice Checks if an object belongs to `_group` group in given context
* @dev Assigning a role to the object makes it a member of a corresponding role group
* @param _objectId ID of an object that is being checked for role group membership
* @param _contextId Context in which membership should be checked
* @param _group name of the role group
* @return true if object with given ID is a member, false otherwise
*/
function isInGroup(bytes32 _objectId, bytes32 _contextId, string memory _group) external view returns (bool) {
return LibACL._isInGroup(_objectId, _contextId, LibHelpers._stringToBytes32(_group));
}

/**
* @notice Check whether a parent object belongs to the `_group` group in given context
* @dev Objects can have a parent object, i.e. entity is a parent of a user
* @param _objectId ID of an object whose parent is being checked for role group membership
* @param _contextId Context in which the role group membership is being checked
* @param _group name of the role group
* @return true if object's parent is a member of this role group, false otherwise
*/
function isParentInGroup(
bytes32 _objectId,
bytes32 _contextId,
string memory _group
)
external
view
returns (bool)
{
return LibACL._isParentInGroup(_objectId, _contextId, LibHelpers._stringToBytes32(_group));
}

/**
* @notice Check whether a user can assign specific object to the `_role` role in given context
* @dev Check permission to assign to a role
* @param _assignerId The object ID of the user who is assigning a role to another object.
* @param _objectId ID of an object that is being checked for assigning rights
* @param _contextId ID of the context in which permission is checked
* @param _role name of the role to check
* @return true if user has the right to assign, false otherwise
*/
function canAssign(
bytes32 _assignerId,
bytes32 _objectId,
bytes32 _contextId,
string memory _role
)
external
view
returns (bool)
{
return LibACL._canAssign(_assignerId, _objectId, _contextId, LibHelpers._stringToBytes32(_role));
}

/**
* @notice Check whether a user can call a specific function.
* @param _userId The object ID of the user who is calling the function.
* @param _contextId ID of the context in which permission is checked.
* @param _groupId ID of the group in which permission is checked.
*/
function hasGroupPrivilege(bytes32 _userId, bytes32 _contextId, bytes32 _groupId) external view returns (bool) {
return LibACL._hasGroupPrivilege(_userId, _contextId, _groupId);
}

/**
* @notice Get a user's (an objectId's) assigned role in a specific context
* @param objectId ID of an object that is being checked for its assigned role in a specific context
* @param contextId ID of the context in which the objectId's role is being checked
* @return roleId objectId's role in the contextId
*/
function getRoleInContext(bytes32 objectId, bytes32 contextId) external view returns (bytes32) {
return LibACL._getRoleInContext(objectId, contextId);
}

/**
* @notice Get whether role is in group.
* @dev Get whether role is in group.
* @param role the role.
* @param group the group.
* @return true if role is in group, false otherwise.
*/
function isRoleInGroup(string memory role, string memory group) external view returns (bool) {
return LibACL._isRoleInGroup(role, group);
}

/**
* @notice Get whether given group can assign given role.
* @dev Get whether given group can assign given role.
* @param role the role.
* @param group the group.
* @return true if role can be assigned by group, false otherwise.
*/
function canGroupAssignRole(string memory role, string memory group) external view returns (bool) {
return LibACL._canGroupAssignRole(role, group);
}

/**
* @notice Update who can assign `_role` role
* @dev Update who has permission to assign this role
* @param _role name of the role
* @param _assignerGroup Group who can assign members to this role
*/
function updateRoleAssigner(
string memory _role,
string memory _assignerGroup
)
external
assertPrivilege(LC.SYSTEM_IDENTIFIER_BYTES32, LC.GROUP_SYSTEM_ADMINS)
{
LibACL._updateRoleAssigner(_role, _assignerGroup);
}

/**
* @notice Update role group membership for `_role` role and `_group` group
* @dev Update role group membership
* @param _role name of the role
* @param _group name of the group
* @param _roleInGroup is member of
*/
function updateRoleGroup(
string memory _role,
string memory _group,
bool _roleInGroup
)
external
assertPrivilege(LC.SYSTEM_IDENTIFIER_BYTES32, LC.GROUP_SYSTEM_ADMINS)
{
require(!strEquals(_group, LC.GROUP_SYSTEM_ADMINS), "system admins group is not modifiable");
LibACL._updateRoleGroup(_role, _group, _roleInGroup);
}

/**
* @notice Compare two strings
* @dev compares keccak256 hashes of ABI encoded strings
* @param s1 first string to compare
* @param s2 second string to compare
* @return true is strings are equal
*/
function strEquals(string memory s1, string memory s2) private pure returns (bool) {
return keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2));
}
}
120 changes: 120 additions & 0 deletions src/facets/GovernanceFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import { IDiamondCut } from "lib/diamond-2-hardhat/contracts/interfaces/IDiamondCut.sol";
import { Modifiers } from "../shared/Modifiers.sol";
import { AppStorage, LibAppStorage } from "../shared/AppStorage.sol";
import { LibGovernance } from "src/libs/LibGovernance.sol";
import { LibConstants as LC } from "src/libs/LibConstants.sol";

contract GovernanceFacet is Modifiers {
event CreateUpgrade(bytes32 id, address indexed who);
event UpdateUpgradeExpiration(uint256 duration);
event UpgradeCancelled(bytes32 id, address indexed who);

/**
* @notice Check if the diamond has been initialized.
* @dev This will get the value from AppStorage.diamondInitialized.
*/
function isDiamondInitialized() external view returns (bool) {
AppStorage storage s = LibAppStorage.diamondStorage();
return s.diamondInitialized;
}

/**
* @notice Calcuate upgrade hash: `id`
* @dev calucate the upgrade hash by hashing all the inputs
* @param _diamondCut the array of FacetCut struct, IDiamondCut.FacetCut[] to be used for upgrade
* @param _init address of the init diamond to be used for upgrade
* @param _calldata bytes to be passed as call data for upgrade
*/
function calculateUpgradeId(
IDiamondCut.FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
)
external
pure
returns (bytes32)
{
return LibGovernance._calculateUpgradeId(_diamondCut, _init, _calldata);
}

/**
* @notice Approve the following upgrade hash: `id`
* @dev The diamondCut() has been modified to check if the upgrade has been scheduled. This method needs to be
* called in order
* for an upgrade to be executed.
* @param id This is the keccak256(abi.encode(cut)), where cut is the array of FacetCut struct,
* IDiamondCut.FacetCut[].
*/
function createUpgrade(bytes32 id) external assertPrivilege(LC.SYSTEM_IDENTIFIER_BYTES32, LC.GROUP_SYSTEM_ADMINS) {
AppStorage storage s = LibAppStorage.diamondStorage();

if (s.upgradeScheduled[id] > block.timestamp) {
revert("Upgrade has already been scheduled");
}
// 0 == upgrade is not scheduled / has been cancelled
// block.timestamp + upgradeExpiration == upgrade is scheduled and expires at this time
// Set back to 0 when an upgrade is complete
s.upgradeScheduled[id] = block.timestamp + s.upgradeExpiration;
emit CreateUpgrade(id, msg.sender);
}

/**
* @notice Update the diamond cut upgrade expiration period.
* @dev When createUpgrade() is called, it allows a diamondCut() upgrade to be executed. This upgrade must be
* executed before the
* upgrade expires. The upgrade expires based on when the upgrade was scheduled (when createUpgrade() was
* called) + AppStorage.upgradeExpiration.
* @param duration The duration until the upgrade expires.
*/
function updateUpgradeExpiration(uint256 duration)
external
assertPrivilege(LC.SYSTEM_IDENTIFIER_BYTES32, LC.GROUP_SYSTEM_ADMINS)
{
AppStorage storage s = LibAppStorage.diamondStorage();

require(1 minutes < duration && duration < 1 weeks, "invalid upgrade expiration period");

s.upgradeExpiration = duration;
emit UpdateUpgradeExpiration(duration);
}

/**
* @notice Cancel the following upgrade hash: `id`
* @dev This will set the mapping AppStorage.upgradeScheduled back to 0.
* @param id This is the keccak256(abi.encode(cut)), where cut is the array of FacetCut struct,
* IDiamondCut.FacetCut[].
*/
function cancelUpgrade(bytes32 id) external assertPrivilege(LC.SYSTEM_IDENTIFIER_BYTES32, LC.GROUP_SYSTEM_ADMINS) {
AppStorage storage s = LibAppStorage.diamondStorage();

require(s.upgradeScheduled[id] > 0, "invalid upgrade ID");

s.upgradeScheduled[id] = 0;

emit UpgradeCancelled(id, msg.sender);
}

/**
* @notice Get the expiry date for provided upgrade hash.
* @dev This will get the value from AppStorage.upgradeScheduled mapping.
* @param id This is the keccak256(abi.encode(cut)), where cut is the array of FacetCut struct,
* IDiamondCut.FacetCut[].
*/
function getUpgrade(bytes32 id) external view returns (uint256 expiry) {
AppStorage storage s = LibAppStorage.diamondStorage();
expiry = s.upgradeScheduled[id];
}

/**
* @notice Get the upgrade expiration period.
* @dev This will get the value from AppStorage.upgradeExpiration. AppStorage.upgradeExpiration is added to the
* block.timestamp to create the upgrade expiration date.
*/
function getUpgradeExpiration() external view returns (uint256 upgradeExpiration) {
AppStorage storage s = LibAppStorage.diamondStorage();
upgradeExpiration = s.upgradeExpiration;
}
}
36 changes: 36 additions & 0 deletions src/facets/NaymsOwnershipFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import { LibDiamond } from "lib/diamond-2-hardhat/contracts/libraries/LibDiamond.sol";
import { IERC173 } from "lib/diamond-2-hardhat/contracts/interfaces/IERC173.sol";
import { LibACL } from "src/libs/LibACL.sol";
import { LibHelpers } from "src/libs/LibHelpers.sol";
import { LibAdmin } from "src/libs/LibAdmin.sol";
import { LibConstants as LC } from "src/libs/LibConstants.sol";
import { Modifiers } from "src/shared/Modifiers.sol";

contract NaymsOwnershipFacet is IERC173, Modifiers {
function transferOwnership(address _newOwner)
external
override
assertPrivilege(LC.SYSTEM_IDENTIFIER_BYTES32, LC.GROUP_SYSTEM_ADMINS)
{
bytes32 systemID = LibHelpers._stringToBytes32(LC.SYSTEM_IDENTIFIER);
bytes32 newAcc1Id = LibHelpers._getIdForAddress(_newOwner);

require(
!LibACL._isInGroup(newAcc1Id, systemID, LibHelpers._stringToBytes32(LC.GROUP_SYSTEM_ADMINS)),
"NEW owner MUST NOT be sys admin"
);
require(
!LibACL._isInGroup(newAcc1Id, systemID, LibHelpers._stringToBytes32(LC.GROUP_SYSTEM_MANAGERS)),
"NEW owner MUST NOT be sys manager"
);

LibDiamond.setContractOwner(_newOwner);
}

function owner() external view override returns (address owner_) {
owner_ = LibDiamond.contractOwner();
}
}
Loading

0 comments on commit aa97324

Please sign in to comment.