Skip to content

Commit

Permalink
feat(Roles): new Roles contract + tests (#99)
Browse files Browse the repository at this point in the history
* feat(Roles): new Roles contract + tests

* style(scripts): prettier

* chore(npm): version bump

* feat: roles full test coverage & live network support

* Update contracts/utils/Roles.sol

Co-authored-by: Dean <[email protected]>

* Update contracts/interfaces/IRoles.sol

Co-authored-by: Dean <[email protected]>

* Update contracts/interfaces/IRoles.sol

Co-authored-by: Dean <[email protected]>

* refactor(Roles): new RoleTransfer contract

* docs(Roles): better NatSpec comments

* refactor(Roles): renaming into RolesBase and Roles

* refactor(Roles): renaming into RolesBase and Roles

* refactor(Roles): moving hasRole view

* feat: full test coverage

* Apply suggestions from code review

* Update contracts/utils/RolesBase.sol

* Apply suggestions from code review

* Update contracts/utils/RolesBase.sol

Co-authored-by: Milap Sheth <[email protected]>

* fix(Roles): always checking roles before transferring

* fix(Roles): add/removeRole event

* refactor(Roles): _addRole to reuse _addRoles

* style(solidity): prettier

---------

Co-authored-by: Dean Amiel <[email protected]>
Co-authored-by: Dean <[email protected]>
Co-authored-by: Milap Sheth <[email protected]>
  • Loading branch information
4 people authored Oct 18, 2023
1 parent 1ba9c07 commit 9ff8488
Show file tree
Hide file tree
Showing 11 changed files with 981 additions and 7 deletions.
74 changes: 74 additions & 0 deletions contracts/interfaces/IRoles.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IRolesBase } from './IRolesBase.sol';

/**
* @title IRoles Interface
* @notice IRoles is an interface that abstracts the implementation of a
* contract with role control features. It's commonly included for the functionality to
* get current role, transfer role, and propose and accept role.
*/
interface IRoles is IRolesBase {
error InvalidProposedAccount(address account);

/**
* @notice Checks if an account has all the roles.
* @param account The address to check
* @param roles The roles to check
* @return True if the account has all the roles, false otherwise
*/
function hasAllTheRoles(address account, uint8[] memory roles) external view returns (bool);

/**
* @notice Checks if an account has any of the roles.
* @param account The address to check
* @param roles The roles to check
* @return True if the account has any of the roles, false otherwise
*/
function hasAnyOfRoles(address account, uint8[] memory roles) external view returns (bool);

/**
* @notice Returns the roles of an account.
* @param account The address to get the roles for
* @return accountRoles The roles of the account in uint256 format
*/
function getAccountRoles(address account) external view returns (uint256 accountRoles);

/**
* @notice Returns the pending role of the contract.
* @param fromAccount The address with the current roles
* @param toAccount The address with the pending roles
* @return proposedRoles_ The pending role of the contract in uint256 format
*/
function getProposedRoles(address fromAccount, address toAccount) external view returns (uint256 proposedRoles_);

/**
* @notice Transfers roles of the contract to a new account.
* @dev Can only be called by the account with all the roles.
* @dev Emits RolesRemoved and RolesAdded events.
* @param toAccount The address to transfer role to
* @param roles The roles to transfer
*/
function transferRoles(address toAccount, uint8[] memory roles) external;

/**
* @notice Propose to transfer roles of message sender to a new account.
* @dev Can only be called by the account with all the proposed roles.
* @dev emits a RolesProposed event.
* @dev Roles are not transferred until the new role accepts the role transfer.
* @param toAccount The address to transfer role to
* @param roles The roles to transfer
*/
function proposeRoles(address toAccount, uint8[] memory roles) external;

/**
* @notice Accepts roles transferred from another account.
* @dev Can only be called by the pending account with all the proposed roles.
* @dev Emits RolesRemoved and RolesAdded events.
* @param fromAccount The address of the current role
* @param roles The roles to accept
*/
function acceptRoles(address fromAccount, uint8[] memory roles) external;
}
28 changes: 28 additions & 0 deletions contracts/interfaces/IRolesBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @title IRolesBase Interface
* @notice IRolesBase is an interface that abstracts the implementation of a
* contract with role control internal functions.
*/
interface IRolesBase {
error MissingRole(address account, uint8 role);
error MissingAllRoles(address account, uint8[] roles);
error MissingAnyOfRoles(address account, uint8[] roles);

error InvalidProposedRoles(address fromAccount, address toAccount, uint8[] roles);

event RolesProposed(address indexed fromAccount, address indexed toAccount, uint8[] roles);
event RolesAdded(address indexed account, uint8[] roles);
event RolesRemoved(address indexed account, uint8[] roles);

/**
* @notice Checks if an account has a role.
* @param account The address to check
* @param role The role to check
* @return True if the account has the role, false otherwise
*/
function hasRole(address account, uint8 role) external view returns (bool);
}
45 changes: 45 additions & 0 deletions contracts/test/utils/TestRoles.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { Roles } from '../../utils/Roles.sol';

contract TestRoles is Roles {
error InvalidRolesLength();

event NumSet(uint256 _num);

uint256 public num;

constructor(address[] memory accounts, uint8[][] memory roleSets) {
uint256 length = accounts.length;
if (length != roleSets.length) revert InvalidRolesLength();

for (uint256 i = 0; i < length; ++i) {
_addRoles(accounts[i], roleSets[i]);
}
}

function setNum(uint256 _num, uint8 role) external onlyRole(role) {
num = _num;
emit NumSet(_num);
}

function setNumWithAllRoles(uint256 _num, uint8[] calldata roles) external withEveryRole(roles) {
num = _num;
emit NumSet(_num);
}

function setNumWithAnyRoles(uint256 _num, uint8[] calldata roles) external withAnyRole(roles) {
num = _num;
emit NumSet(_num);
}

function addRole(address account, uint8 role) external {
_addRole(account, role);
}

function removeRole(address account, uint8 role) external {
_removeRole(account, role);
}
}
94 changes: 94 additions & 0 deletions contracts/utils/Roles.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IRoles } from '../interfaces/IRoles.sol';
import { RolesBase } from './RolesBase.sol';

/**
* @title Roles
* @notice A contract module which provides set of external functions providing basic role transferring functionality.
*
* @notice The role account is set through role transfer. This module makes
* it possible to transfer the role of the contract to a new account in one
* step, as well as to an interim pending role. In the second flow the role does not
* change until the pending role accepts the role transfer.
*/
contract Roles is RolesBase, IRoles {
/**
* @notice Checks if an account has all the roles.
* @param account The address to check
* @param roles The roles to check
* @return True if the account has all the roles, false otherwise
*/
function hasAllTheRoles(address account, uint8[] memory roles) public view returns (bool) {
return _hasAllTheRoles(_getRoles(account), roles);
}

/**
* @notice Checks if an account has any of the roles.
* @param account The address to check
* @param roles The roles to check
* @return True if the account has any of the roles, false otherwise
*/
function hasAnyOfRoles(address account, uint8[] memory roles) public view returns (bool) {
return _hasAnyOfRoles(_getRoles(account), roles);
}

/**
* @notice Returns the roles of an account.
* @param account The address to get the roles for
* @return accountRoles The roles of the account in uint256 format
*/
function getAccountRoles(address account) public view returns (uint256 accountRoles) {
accountRoles = _getRoles(account);
}

/**
* @notice Returns the pending role of the contract.
* @param fromAccount The address with the current roles
* @param toAccount The address with the pending roles
* @return proposedRoles_ The pending role of the contract in uint256 format
*/
function getProposedRoles(address fromAccount, address toAccount) public view returns (uint256 proposedRoles_) {
proposedRoles_ = _getProposedRoles(fromAccount, toAccount);
}

/**
* @notice Propose to transfer roles of message sender to a new account.
* @dev Can only be called by the account with all the proposed roles.
* @dev emits a RolesProposed event.
* @dev Roles are not transferred until the new role accepts the role transfer.
* @param toAccount The address to transfer role to
* @param roles The roles to transfer
*/
function proposeRoles(address toAccount, uint8[] memory roles) external virtual {
if (toAccount == address(0) || toAccount == msg.sender) revert InvalidProposedAccount(toAccount);

_proposeRoles(msg.sender, toAccount, roles);
}

/**
* @notice Accepts roles transferred from another account.
* @dev Can only be called by the pending account with all the proposed roles.
* @dev Emits RolesRemoved and RolesAdded events.
* @param fromAccount The address of the current role
* @param roles The roles to accept
*/
function acceptRoles(address fromAccount, uint8[] memory roles) external virtual {
_acceptRoles(fromAccount, msg.sender, roles);
}

/**
* @notice Transfers roles of the contract to a new account.
* @dev Can only be called by the account with all the roles.
* @dev Emits RolesRemoved and RolesAdded events.
* @param toAccount The address to transfer role to
* @param roles The roles to transfer
*/
function transferRoles(address toAccount, uint8[] memory roles) external virtual {
if (toAccount == address(0) || toAccount == msg.sender) revert InvalidProposedAccount(toAccount);

_transferRoles(msg.sender, toAccount, roles);
}
}
Loading

0 comments on commit 9ff8488

Please sign in to comment.