Files

238 lines
9.1 KiB
Solidity

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;
library Exclusive {
struct RoleMembership {
address member;
}
function isMember(RoleMembership storage roleMembership, address memberToCheck) internal view returns (bool) {
return roleMembership.member == memberToCheck;
}
function resetMember(RoleMembership storage roleMembership, address newMember) internal {
require(newMember != address(0x0), "Cannot set an exclusive role to 0x0");
roleMembership.member = newMember;
}
function getMember(RoleMembership storage roleMembership) internal view returns (address) {
return roleMembership.member;
}
function init(RoleMembership storage roleMembership, address initialMember) internal {
resetMember(roleMembership, initialMember);
}
}
library Shared {
struct RoleMembership {
mapping(address => bool) members;
}
function isMember(RoleMembership storage roleMembership, address memberToCheck) internal view returns (bool) {
return roleMembership.members[memberToCheck];
}
function addMember(RoleMembership storage roleMembership, address memberToAdd) internal {
require(memberToAdd != address(0x0), "Cannot add 0x0 to a shared role");
roleMembership.members[memberToAdd] = true;
}
function removeMember(RoleMembership storage roleMembership, address memberToRemove) internal {
roleMembership.members[memberToRemove] = false;
}
function init(RoleMembership storage roleMembership, address[] memory initialMembers) internal {
for (uint256 i = 0; i < initialMembers.length; i++) {
addMember(roleMembership, initialMembers[i]);
}
}
}
/**
* @title Base class to manage permissions for the derived class.
*/
abstract contract MultiRole {
using Exclusive for Exclusive.RoleMembership;
using Shared for Shared.RoleMembership;
enum RoleType { Invalid, Exclusive, Shared }
struct Role {
uint256 managingRole;
RoleType roleType;
Exclusive.RoleMembership exclusiveRoleMembership;
Shared.RoleMembership sharedRoleMembership;
}
mapping(uint256 => Role) private roles;
event ResetExclusiveMember(uint256 indexed roleId, address indexed newMember, address indexed manager);
event AddedSharedMember(uint256 indexed roleId, address indexed newMember, address indexed manager);
event RemovedSharedMember(uint256 indexed roleId, address indexed oldMember, address indexed manager);
/**
* @notice Reverts unless the caller is a member of the specified roleId.
*/
modifier onlyRoleHolder(uint256 roleId) {
require(holdsRole(roleId, msg.sender), "Sender does not hold required role");
_;
}
/**
* @notice Reverts unless the caller is a member of the manager role for the specified roleId.
*/
modifier onlyRoleManager(uint256 roleId) {
require(holdsRole(roles[roleId].managingRole, msg.sender), "Can only be called by a role manager");
_;
}
/**
* @notice Reverts unless the roleId represents an initialized, exclusive roleId.
*/
modifier onlyExclusive(uint256 roleId) {
require(roles[roleId].roleType == RoleType.Exclusive, "Must be called on an initialized Exclusive role");
_;
}
/**
* @notice Reverts unless the roleId represents an initialized, shared roleId.
*/
modifier onlyShared(uint256 roleId) {
require(roles[roleId].roleType == RoleType.Shared, "Must be called on an initialized Shared role");
_;
}
/**
* @notice Whether `memberToCheck` is a member of roleId.
* @dev Reverts if roleId does not correspond to an initialized role.
* @param roleId the Role to check.
* @param memberToCheck the address to check.
* @return True if `memberToCheck` is a member of `roleId`.
*/
function holdsRole(uint256 roleId, address memberToCheck) public view returns (bool) {
Role storage role = roles[roleId];
if (role.roleType == RoleType.Exclusive) {
return role.exclusiveRoleMembership.isMember(memberToCheck);
} else if (role.roleType == RoleType.Shared) {
return role.sharedRoleMembership.isMember(memberToCheck);
}
revert("Invalid roleId");
}
/**
* @notice Changes the exclusive role holder of `roleId` to `newMember`.
* @dev Reverts if the caller is not a member of the managing role for `roleId` or if `roleId` is not an
* initialized, ExclusiveRole.
* @param roleId the ExclusiveRole membership to modify.
* @param newMember the new ExclusiveRole member.
*/
function resetMember(uint256 roleId, address newMember) public onlyExclusive(roleId) onlyRoleManager(roleId) {
roles[roleId].exclusiveRoleMembership.resetMember(newMember);
emit ResetExclusiveMember(roleId, newMember, msg.sender);
}
/**
* @notice Gets the current holder of the exclusive role, `roleId`.
* @dev Reverts if `roleId` does not represent an initialized, exclusive role.
* @param roleId the ExclusiveRole membership to check.
* @return the address of the current ExclusiveRole member.
*/
function getMember(uint256 roleId) public view onlyExclusive(roleId) returns (address) {
return roles[roleId].exclusiveRoleMembership.getMember();
}
/**
* @notice Adds `newMember` to the shared role, `roleId`.
* @dev Reverts if `roleId` does not represent an initialized, SharedRole or if the caller is not a member of the
* managing role for `roleId`.
* @param roleId the SharedRole membership to modify.
* @param newMember the new SharedRole member.
*/
function addMember(uint256 roleId, address newMember) public onlyShared(roleId) onlyRoleManager(roleId) {
roles[roleId].sharedRoleMembership.addMember(newMember);
emit AddedSharedMember(roleId, newMember, msg.sender);
}
/**
* @notice Removes `memberToRemove` from the shared role, `roleId`.
* @dev Reverts if `roleId` does not represent an initialized, SharedRole or if the caller is not a member of the
* managing role for `roleId`.
* @param roleId the SharedRole membership to modify.
* @param memberToRemove the current SharedRole member to remove.
*/
function removeMember(uint256 roleId, address memberToRemove) public onlyShared(roleId) onlyRoleManager(roleId) {
roles[roleId].sharedRoleMembership.removeMember(memberToRemove);
emit RemovedSharedMember(roleId, memberToRemove, msg.sender);
}
/**
* @notice Removes caller from the role, `roleId`.
* @dev Reverts if the caller is not a member of the role for `roleId` or if `roleId` is not an
* initialized, SharedRole.
* @param roleId the SharedRole membership to modify.
*/
function renounceMembership(uint256 roleId) public onlyShared(roleId) onlyRoleHolder(roleId) {
roles[roleId].sharedRoleMembership.removeMember(msg.sender);
emit RemovedSharedMember(roleId, msg.sender, msg.sender);
}
/**
* @notice Reverts if `roleId` is not initialized.
*/
modifier onlyValidRole(uint256 roleId) {
require(roles[roleId].roleType != RoleType.Invalid, "Attempted to use an invalid roleId");
_;
}
/**
* @notice Reverts if `roleId` is initialized.
*/
modifier onlyInvalidRole(uint256 roleId) {
require(roles[roleId].roleType == RoleType.Invalid, "Cannot use a pre-existing role");
_;
}
/**
* @notice Internal method to initialize a shared role, `roleId`, which will be managed by `managingRoleId`.
* `initialMembers` will be immediately added to the role.
* @dev Should be called by derived contracts, usually at construction time. Will revert if the role is already
* initialized.
*/
function _createSharedRole(
uint256 roleId,
uint256 managingRoleId,
address[] memory initialMembers
) internal onlyInvalidRole(roleId) {
Role storage role = roles[roleId];
role.roleType = RoleType.Shared;
role.managingRole = managingRoleId;
role.sharedRoleMembership.init(initialMembers);
require(
roles[managingRoleId].roleType != RoleType.Invalid,
"Attempted to use an invalid role to manage a shared role"
);
}
/**
* @notice Internal method to initialize an exclusive role, `roleId`, which will be managed by `managingRoleId`.
* `initialMember` will be immediately added to the role.
* @dev Should be called by derived contracts, usually at construction time. Will revert if the role is already
* initialized.
*/
function _createExclusiveRole(
uint256 roleId,
uint256 managingRoleId,
address initialMember
) internal onlyInvalidRole(roleId) {
Role storage role = roles[roleId];
role.roleType = RoleType.Exclusive;
role.managingRole = managingRoleId;
role.exclusiveRoleMembership.init(initialMember);
require(
roles[managingRoleId].roleType != RoleType.Invalid,
"Attempted to use an invalid role to manage an exclusive role"
);
}
}