non-fungible-apps/contracts/contracts/FleekAccessControl.sol

180 lines
5.9 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
error MustHaveCollectionRole(uint8 role);
error MustHaveTokenRole(uint256 tokenId, uint8 role);
error MustHaveAtLeastOneOwner();
error RoleAlreadySet();
contract FleekAccessControl is Initializable {
/**
* @dev All available collection roles.
*/
enum CollectionRoles {
Owner,
Verifier
}
/**
* @dev All available token roles.
*/
enum TokenRoles {
Controller
}
/**
* @dev Emitted when a token role is changed.
*/
event TokenRoleChanged(
uint256 indexed tokenId,
TokenRoles indexed role,
address indexed toAddress,
bool status,
address byAddress
);
/**
* @dev Emitted when token roles version is increased and all token roles are cleared.
*/
event TokenRolesCleared(uint256 indexed tokenId, address byAddress);
/**
* @dev Emitted when a collection role is changed.
*/
event CollectionRoleChanged(
CollectionRoles indexed role,
address indexed toAddress,
bool status,
address byAddress
);
/**
* @dev _collectionRolesCounter[role] is the number of addresses that have the role.
* This is prevent Owner role to go to 0.
*/
mapping(CollectionRoles => uint256) private _collectionRolesCounter;
/**
* @dev _collectionRoles[role][address] is the mapping of addresses that have the role.
*/
mapping(CollectionRoles => mapping(address => bool)) private _collectionRoles;
/**
* @dev _tokenRolesVersion[tokenId] is the version of the token roles.
* The version is incremented every time the token roles are cleared.
* Should be incremented every token transfer.
*/
mapping(uint256 => uint256) private _tokenRolesVersion;
/**
* @dev _tokenRoles[tokenId][version][role][address] is the mapping of addresses that have the role.
*/
mapping(uint256 => mapping(uint256 => mapping(TokenRoles => mapping(address => bool)))) private _tokenRoles;
/**
* @dev Initializes the contract by granting the `Owner` role to the deployer.
*/
function __FleekAccessControl_init() internal onlyInitializing {
_grantCollectionRole(CollectionRoles.Owner, msg.sender);
_grantCollectionRole(CollectionRoles.Verifier, msg.sender);
}
/**
* @dev Checks if the `msg.sender` has a certain role.
*/
function _requireCollectionRole(CollectionRoles role) internal view {
if (!hasCollectionRole(role, msg.sender)) revert MustHaveCollectionRole(uint8(role));
}
/**
* @dev Checks if the `msg.sender` has the `Token` role for a certain `tokenId`.
*/
function _requireTokenRole(uint256 tokenId, TokenRoles role) internal view {
if (!hasTokenRole(tokenId, role, msg.sender)) revert MustHaveTokenRole(tokenId, uint8(role));
}
/**
* @dev Returns `True` if a certain address has the collection role.
*/
function hasCollectionRole(CollectionRoles role, address account) public view returns (bool) {
return _collectionRoles[role][account];
}
/**
* @dev Returns `True` if a certain address has the token role.
*/
function hasTokenRole(uint256 tokenId, TokenRoles role, address account) public view returns (bool) {
uint256 currentVersion = _tokenRolesVersion[tokenId];
return _tokenRoles[tokenId][currentVersion][role][account];
}
/**
* @dev Grants the collection role to an address.
*/
function _grantCollectionRole(CollectionRoles role, address account) internal {
if (hasCollectionRole(role, account)) revert RoleAlreadySet();
_collectionRoles[role][account] = true;
_collectionRolesCounter[role] += 1;
emit CollectionRoleChanged(role, account, true, msg.sender);
}
/**
* @dev Revokes the collection role of an address.
*/
function _revokeCollectionRole(CollectionRoles role, address account) internal {
if (!hasCollectionRole(role, account)) revert RoleAlreadySet();
if (role == CollectionRoles.Owner && _collectionRolesCounter[role] == 1) revert MustHaveAtLeastOneOwner();
_collectionRoles[role][account] = false;
_collectionRolesCounter[role] -= 1;
emit CollectionRoleChanged(role, account, false, msg.sender);
}
/**
* @dev Grants the token role to an address.
*/
function _grantTokenRole(uint256 tokenId, TokenRoles role, address account) internal {
if (hasTokenRole(tokenId, role, account)) revert RoleAlreadySet();
uint256 currentVersion = _tokenRolesVersion[tokenId];
_tokenRoles[tokenId][currentVersion][role][account] = true;
emit TokenRoleChanged(tokenId, role, account, true, msg.sender);
}
/**
* @dev Revokes the token role of an address.
*/
function _revokeTokenRole(uint256 tokenId, TokenRoles role, address account) internal {
if (!hasTokenRole(tokenId, role, account)) revert RoleAlreadySet();
uint256 currentVersion = _tokenRolesVersion[tokenId];
_tokenRoles[tokenId][currentVersion][role][account] = false;
emit TokenRoleChanged(tokenId, role, account, false, msg.sender);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
function _clearTokenRoles(uint256 tokenId) internal {
_tokenRolesVersion[tokenId] += 1;
emit TokenRolesCleared(tokenId, msg.sender);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}