refactor: clean up acl remove our redundant owner (#114)

* refactor: clean up code, remove address arrays, fix minimal amount of collection owners

* refactor: apply refactored acl functions in main contract

* test: fix tests after refactor

* test: add edge case foundry tests for acl

* test: fix hardhat tests for token roles

* test: fix hardhat tests for collection roles

* test: fix hardhat tests for minting

* test: add new hardhat tests for access control

* refactor: role change events
This commit is contained in:
Felipe Mendes 2023-02-21 14:02:30 -03:00 committed by GitHub
parent 0f05a912a7
commit 770ab78668
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 514 additions and 507 deletions

View File

@ -5,172 +5,170 @@ pragma solidity ^0.8.7;
import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 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 { contract FleekAccessControl is Initializable {
using Counters for Counters.Counter; using Counters for Counters.Counter;
enum Roles { /**
Owner, * @dev All available collection roles.
*/
enum CollectionRoles {
Owner
}
/**
* @dev All available token roles.
*/
enum TokenRoles {
Controller Controller
} }
event TokenRoleGranted(uint256 indexed tokenId, Roles indexed role, address indexed toAddress, address byAddress); /**
event TokenRoleRevoked(uint256 indexed tokenId, Roles indexed role, address indexed toAddress, address byAddress); * @dev Emitted when a token role is changed.
event CollectionRoleGranted(Roles indexed role, address indexed toAddress, address byAddress); */
event CollectionRoleRevoked(Roles indexed role, address indexed toAddress, address byAddress); event TokenRoleChanged(
uint256 indexed tokenId,
TokenRoles indexed role,
address indexed toAddress,
bool status,
address byAddress
);
struct Role { /**
mapping(address => uint256) indexes; * @dev Emitted when token roles version is increased and all token roles are cleared.
address[] members; */
} event TokenRolesCleared(uint256 indexed tokenId, address byAddress);
Counters.Counter private _collectionRolesVersion; /**
// _collectionRoles[version][role] * @dev Emitted when a collection role is changed.
mapping(uint256 => mapping(Roles => Role)) private _collectionRoles; */
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 => Counters.Counter) 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 => Counters.Counter) private _tokenRolesVersion; mapping(uint256 => Counters.Counter) private _tokenRolesVersion;
// _tokenRoles[tokenId][version][role]
mapping(uint256 => mapping(uint256 => mapping(Roles => Role))) private _tokenRoles; /**
* @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. * @dev Initializes the contract by granting the `Owner` role to the deployer.
*/ */
function __FleekAccessControl_init() internal onlyInitializing { function __FleekAccessControl_init() internal onlyInitializing {
_grantCollectionRole(Roles.Owner, msg.sender); _grantCollectionRole(CollectionRoles.Owner, msg.sender);
} }
/** /**
* @dev Checks if the `msg.sender` has a certain role. * @dev Checks if the `msg.sender` has a certain role.
*/ */
modifier requireCollectionRole(Roles role) { function _requireCollectionRole(CollectionRoles role) internal view {
require( if (!hasCollectionRole(role, msg.sender)) revert MustHaveCollectionRole(uint8(role));
hasCollectionRole(role, msg.sender) || hasCollectionRole(Roles.Owner, msg.sender),
"FleekAccessControl: must have collection role"
);
_;
} }
/** /**
* @dev Checks if the `msg.sender` has the `Token` role for a certain `tokenId`. * @dev Checks if the `msg.sender` has the `Token` role for a certain `tokenId`.
*/ */
modifier requireTokenRole(uint256 tokenId, Roles role) { function _requireTokenRole(uint256 tokenId, TokenRoles role) internal view {
require( if (!hasTokenRole(tokenId, role, msg.sender)) revert MustHaveTokenRole(tokenId, uint8(role));
hasTokenRole(tokenId, role, msg.sender) || hasTokenRole(tokenId, Roles.Owner, msg.sender),
"FleekAccessControl: must have token role"
);
_;
} }
/** /**
* @dev Returns `True` if a certain address has the collection role. * @dev Returns `True` if a certain address has the collection role.
*/ */
function hasCollectionRole(Roles role, address account) public view returns (bool) { function hasCollectionRole(CollectionRoles role, address account) public view returns (bool) {
uint256 currentVersion = _collectionRolesVersion.current(); return _collectionRoles[role][account];
return _collectionRoles[currentVersion][role].indexes[account] != 0;
} }
/** /**
* @dev Returns `True` if a certain address has the token role. * @dev Returns `True` if a certain address has the token role.
*/ */
function hasTokenRole(uint256 tokenId, Roles role, address account) public view returns (bool) { function hasTokenRole(uint256 tokenId, TokenRoles role, address account) public view returns (bool) {
uint256 currentVersion = _tokenRolesVersion[tokenId].current(); uint256 currentVersion = _tokenRolesVersion[tokenId].current();
return _tokenRoles[tokenId][currentVersion][role].indexes[account] != 0; return _tokenRoles[tokenId][currentVersion][role][account];
}
/**
* @dev Returns an array of addresses that all have the collection role.
*/
function getCollectionRoleMembers(Roles role) public view returns (address[] memory) {
uint256 currentVersion = _collectionRolesVersion.current();
return _collectionRoles[currentVersion][role].members;
}
/**
* @dev Returns an array of addresses that all have the same token role for a certain tokenId.
*/
function getTokenRoleMembers(uint256 tokenId, Roles role) public view returns (address[] memory) {
uint256 currentVersion = _tokenRolesVersion[tokenId].current();
return _tokenRoles[tokenId][currentVersion][role].members;
} }
/** /**
* @dev Grants the collection role to an address. * @dev Grants the collection role to an address.
*/ */
function _grantCollectionRole(Roles role, address account) internal { function _grantCollectionRole(CollectionRoles role, address account) internal {
uint256 currentVersion = _collectionRolesVersion.current(); if (hasCollectionRole(role, account)) revert RoleAlreadySet();
_grantRole(_collectionRoles[currentVersion][role], account);
emit CollectionRoleGranted(role, account, msg.sender); _collectionRoles[role][account] = true;
_collectionRolesCounter[role].increment();
emit CollectionRoleChanged(role, account, true, msg.sender);
} }
/** /**
* @dev Revokes the collection role of an address. * @dev Revokes the collection role of an address.
*/ */
function _revokeCollectionRole(Roles role, address account) internal { function _revokeCollectionRole(CollectionRoles role, address account) internal {
uint256 currentVersion = _collectionRolesVersion.current(); if (!hasCollectionRole(role, account)) revert RoleAlreadySet();
_revokeRole(_collectionRoles[currentVersion][role], account); if (role == CollectionRoles.Owner && _collectionRolesCounter[role].current() == 1)
emit CollectionRoleRevoked(role, account, msg.sender); revert MustHaveAtLeastOneOwner();
_collectionRoles[role][account] = false;
_collectionRolesCounter[role].decrement();
emit CollectionRoleChanged(role, account, false, msg.sender);
} }
/** /**
* @dev Grants the token role to an address. * @dev Grants the token role to an address.
*/ */
function _grantTokenRole(uint256 tokenId, Roles role, address account) internal { function _grantTokenRole(uint256 tokenId, TokenRoles role, address account) internal {
if (hasTokenRole(tokenId, role, account)) revert RoleAlreadySet();
uint256 currentVersion = _tokenRolesVersion[tokenId].current(); uint256 currentVersion = _tokenRolesVersion[tokenId].current();
_grantRole(_tokenRoles[tokenId][currentVersion][role], account); _tokenRoles[tokenId][currentVersion][role][account] = true;
emit TokenRoleGranted(tokenId, role, account, msg.sender);
emit TokenRoleChanged(tokenId, role, account, true, msg.sender);
} }
/** /**
* @dev Revokes the token role of an address. * @dev Revokes the token role of an address.
*/ */
function _revokeTokenRole(uint256 tokenId, Roles role, address account) internal { function _revokeTokenRole(uint256 tokenId, TokenRoles role, address account) internal {
if (!hasTokenRole(tokenId, role, account)) revert RoleAlreadySet();
uint256 currentVersion = _tokenRolesVersion[tokenId].current(); uint256 currentVersion = _tokenRolesVersion[tokenId].current();
_revokeRole(_tokenRoles[tokenId][currentVersion][role], account); _tokenRoles[tokenId][currentVersion][role][account] = false;
emit TokenRoleRevoked(tokenId, role, account, msg.sender);
}
/** emit TokenRoleChanged(tokenId, role, account, false, msg.sender);
* @dev Grants a certain role to a certain address.
*/
function _grantRole(Role storage role, address account) internal {
if (role.indexes[account] == 0) {
role.members.push(account);
role.indexes[account] = role.members.length;
}
}
/**
* @dev Revokes a certain role from a certain address.
*/
function _revokeRole(Role storage role, address account) internal {
if (role.indexes[account] != 0) {
uint256 index = role.indexes[account] - 1;
uint256 lastIndex = role.members.length - 1;
address lastAccount = role.members[lastIndex];
role.members[index] = lastAccount;
role.indexes[lastAccount] = index + 1;
role.members.pop();
delete role.indexes[account];
}
}
/**
* @dev Clears all token roles for a certain tokenId.
* Should only be used for burning tokens.
*/
function _clearAllTokenRoles(uint256 tokenId) internal {
_tokenRolesVersion[tokenId].increment();
} }
/** /**
* @dev Clears all token roles for a certain tokenId and grants the owner role to a new address. * @dev Clears all token roles for a certain tokenId and grants the owner role to a new address.
* Should only be used for transferring tokens. * Should only be used for transferring tokens.
*/ */
function _clearAllTokenRoles(uint256 tokenId, address newOwner) internal { function _clearTokenRoles(uint256 tokenId) internal {
_clearAllTokenRoles(tokenId); _tokenRolesVersion[tokenId].increment();
_grantTokenRole(tokenId, Roles.Owner, newOwner); emit TokenRolesCleared(tokenId, msg.sender);
} }
/** /**

View File

@ -10,6 +10,7 @@ import "./FleekAccessControl.sol";
import "./util/FleekStrings.sol"; import "./util/FleekStrings.sol";
import "./FleekPausable.sol"; import "./FleekPausable.sol";
error MustBeTokenOwner(uint256 tokenId);
error ThereIsNoTokenMinted(); error ThereIsNoTokenMinted();
contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, FleekPausable { contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, FleekPausable {
@ -117,7 +118,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
string memory gitRepository, string memory gitRepository,
string memory logo, string memory logo,
uint24 color uint24 color
) public payable requireCollectionRole(Roles.Owner) returns (uint256) { ) public payable requireCollectionRole(CollectionRoles.Owner) returns (uint256) {
uint256 tokenId = _appIds.current(); uint256 tokenId = _appIds.current();
_mint(to, tokenId); _mint(to, tokenId);
_appIds.increment(); _appIds.increment();
@ -206,13 +207,13 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
) internal virtual override whenNotPaused { ) internal virtual override whenNotPaused {
if (from != address(0) && to != address(0)) { if (from != address(0) && to != address(0)) {
// Transfer // Transfer
_clearAllTokenRoles(tokenId, to); _clearTokenRoles(tokenId);
} else if (from == address(0)) { } else if (from == address(0)) {
// Mint // Mint
_grantTokenRole(tokenId, Roles.Owner, to); // TODO: set contract owner as controller
} else if (to == address(0)) { } else if (to == address(0)) {
// Burn // Burn
_clearAllTokenRoles(tokenId); _clearTokenRoles(tokenId);
} }
super._beforeTokenTransfer(from, to, tokenId, batchSize); super._beforeTokenTransfer(from, to, tokenId, batchSize);
} }
@ -238,7 +239,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
function setTokenExternalURL( function setTokenExternalURL(
uint256 tokenId, uint256 tokenId,
string memory _tokenExternalURL string memory _tokenExternalURL
) public virtual requireTokenRole(tokenId, Roles.Controller) { ) public virtual requireTokenRole(tokenId, TokenRoles.Controller) {
_requireMinted(tokenId); _requireMinted(tokenId);
_apps[tokenId].externalURL = _tokenExternalURL; _apps[tokenId].externalURL = _tokenExternalURL;
emit MetadataUpdate(tokenId, "externalURL", _tokenExternalURL, msg.sender); emit MetadataUpdate(tokenId, "externalURL", _tokenExternalURL, msg.sender);
@ -258,7 +259,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
function setTokenENS( function setTokenENS(
uint256 tokenId, uint256 tokenId,
string memory _tokenENS string memory _tokenENS
) public virtual requireTokenRole(tokenId, Roles.Controller) { ) public virtual requireTokenRole(tokenId, TokenRoles.Controller) {
_requireMinted(tokenId); _requireMinted(tokenId);
_apps[tokenId].ENS = _tokenENS; _apps[tokenId].ENS = _tokenENS;
emit MetadataUpdate(tokenId, "ENS", _tokenENS, msg.sender); emit MetadataUpdate(tokenId, "ENS", _tokenENS, msg.sender);
@ -278,7 +279,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
function setTokenName( function setTokenName(
uint256 tokenId, uint256 tokenId,
string memory _tokenName string memory _tokenName
) public virtual requireTokenRole(tokenId, Roles.Controller) { ) public virtual requireTokenRole(tokenId, TokenRoles.Controller) {
_requireMinted(tokenId); _requireMinted(tokenId);
_apps[tokenId].name = _tokenName; _apps[tokenId].name = _tokenName;
emit MetadataUpdate(tokenId, "name", _tokenName, msg.sender); emit MetadataUpdate(tokenId, "name", _tokenName, msg.sender);
@ -298,7 +299,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
function setTokenDescription( function setTokenDescription(
uint256 tokenId, uint256 tokenId,
string memory _tokenDescription string memory _tokenDescription
) public virtual requireTokenRole(tokenId, Roles.Controller) { ) public virtual requireTokenRole(tokenId, TokenRoles.Controller) {
_requireMinted(tokenId); _requireMinted(tokenId);
_apps[tokenId].description = _tokenDescription; _apps[tokenId].description = _tokenDescription;
emit MetadataUpdate(tokenId, "description", _tokenDescription, msg.sender); emit MetadataUpdate(tokenId, "description", _tokenDescription, msg.sender);
@ -318,7 +319,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
function setTokenLogo( function setTokenLogo(
uint256 tokenId, uint256 tokenId,
string memory _tokenLogo string memory _tokenLogo
) public virtual requireTokenRole(tokenId, Roles.Controller) { ) public virtual requireTokenRole(tokenId, TokenRoles.Controller) {
_requireMinted(tokenId); _requireMinted(tokenId);
_apps[tokenId].logo = _tokenLogo; _apps[tokenId].logo = _tokenLogo;
emit MetadataUpdate(tokenId, "logo", _tokenLogo, msg.sender); emit MetadataUpdate(tokenId, "logo", _tokenLogo, msg.sender);
@ -338,7 +339,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
function setTokenColor( function setTokenColor(
uint256 tokenId, uint256 tokenId,
uint24 _tokenColor uint24 _tokenColor
) public virtual requireTokenRole(tokenId, Roles.Controller) { ) public virtual requireTokenRole(tokenId, TokenRoles.Controller) {
_requireMinted(tokenId); _requireMinted(tokenId);
_apps[tokenId].color = _tokenColor; _apps[tokenId].color = _tokenColor;
emit MetadataUpdate(tokenId, "color", _tokenColor, msg.sender); emit MetadataUpdate(tokenId, "color", _tokenColor, msg.sender);
@ -476,7 +477,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
function setAccessPointContentVerify( function setAccessPointContentVerify(
string memory apName, string memory apName,
bool verified bool verified
) public requireAP(apName) requireTokenRole(_accessPoints[apName].tokenId, Roles.Controller) { ) public requireAP(apName) requireTokenRole(_accessPoints[apName].tokenId, TokenRoles.Controller) {
_accessPoints[apName].contentVerified = verified; _accessPoints[apName].contentVerified = verified;
emit ChangeAccessPointContentVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender); emit ChangeAccessPointContentVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender);
} }
@ -495,7 +496,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
function setAccessPointNameVerify( function setAccessPointNameVerify(
string memory apName, string memory apName,
bool verified bool verified
) public requireAP(apName) requireTokenRole(_accessPoints[apName].tokenId, Roles.Controller) { ) public requireAP(apName) requireTokenRole(_accessPoints[apName].tokenId, TokenRoles.Controller) {
_accessPoints[apName].nameVerified = verified; _accessPoints[apName].nameVerified = verified;
emit ChangeAccessPointNameVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender); emit ChangeAccessPointNameVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender);
} }
@ -515,7 +516,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
uint256 tokenId, uint256 tokenId,
string memory _commitHash, string memory _commitHash,
string memory _gitRepository string memory _gitRepository
) public virtual requireTokenRole(tokenId, Roles.Controller) { ) public virtual requireTokenRole(tokenId, TokenRoles.Controller) {
_requireMinted(tokenId); _requireMinted(tokenId);
_apps[tokenId].builds[++_apps[tokenId].currentBuild] = Build(_commitHash, _gitRepository); _apps[tokenId].builds[++_apps[tokenId].currentBuild] = Build(_commitHash, _gitRepository);
emit MetadataUpdate(tokenId, "build", [_commitHash, _gitRepository], msg.sender); emit MetadataUpdate(tokenId, "build", [_commitHash, _gitRepository], msg.sender);
@ -529,11 +530,11 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
* Requirements: * Requirements:
* *
* - the tokenId must be minted and valid. * - the tokenId must be minted and valid.
* - the sender must have the `tokenOwner` role. * - the sender must be the owner of the token.
* - the contract must be not paused. * - the contract must be not paused.
* *
*/ */
function burn(uint256 tokenId) public virtual requireTokenRole(tokenId, Roles.Owner) { function burn(uint256 tokenId) public virtual requireTokenOwner(tokenId) {
super._burn(tokenId); super._burn(tokenId);
if (bytes(_apps[tokenId].externalURL).length != 0) { if (bytes(_apps[tokenId].externalURL).length != 0) {
@ -545,6 +546,30 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
ACCESS CONTROL ACCESS CONTROL
//////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////*/
/**
* @dev Requires caller to have a selected collection role.
*/
modifier requireCollectionRole(CollectionRoles role) {
_requireCollectionRole(role);
_;
}
/**
* @dev Requires caller to have a selected token role.
*/
modifier requireTokenRole(uint256 tokenId, TokenRoles role) {
if (ownerOf(tokenId) != msg.sender) _requireTokenRole(tokenId, role);
_;
}
/**
* @dev Requires caller to be selected token owner.
*/
modifier requireTokenOwner(uint256 tokenId) {
if (ownerOf(tokenId) != msg.sender) revert MustBeTokenOwner(tokenId);
_;
}
/** /**
* @dev Grants the collection role to an address. * @dev Grants the collection role to an address.
* *
@ -553,7 +578,10 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
* - the caller should have the collection role. * - the caller should have the collection role.
* *
*/ */
function grantCollectionRole(Roles role, address account) public whenNotPaused requireCollectionRole(Roles.Owner) { function grantCollectionRole(
CollectionRoles role,
address account
) public whenNotPaused requireCollectionRole(CollectionRoles.Owner) {
_grantCollectionRole(role, account); _grantCollectionRole(role, account);
} }
@ -567,9 +595,9 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
*/ */
function grantTokenRole( function grantTokenRole(
uint256 tokenId, uint256 tokenId,
Roles role, TokenRoles role,
address account address account
) public whenNotPaused requireTokenRole(tokenId, Roles.Owner) { ) public whenNotPaused requireTokenOwner(tokenId) {
_grantTokenRole(tokenId, role, account); _grantTokenRole(tokenId, role, account);
} }
@ -581,7 +609,10 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
* - the caller should have the collection role. * - the caller should have the collection role.
* *
*/ */
function revokeCollectionRole(Roles role, address account) public whenNotPaused requireCollectionRole(Roles.Owner) { function revokeCollectionRole(
CollectionRoles role,
address account
) public whenNotPaused requireCollectionRole(CollectionRoles.Owner) {
_revokeCollectionRole(role, account); _revokeCollectionRole(role, account);
} }
@ -595,9 +626,9 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
*/ */
function revokeTokenRole( function revokeTokenRole(
uint256 tokenId, uint256 tokenId,
Roles role, TokenRoles role,
address account address account
) public whenNotPaused requireTokenRole(tokenId, Roles.Owner) { ) public whenNotPaused requireTokenOwner(tokenId) {
_revokeTokenRole(tokenId, role, account); _revokeTokenRole(tokenId, role, account);
} }
@ -615,7 +646,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
* - the contract must be not paused. * - the contract must be not paused.
* *
*/ */
function pause() public requireCollectionRole(Roles.Controller) { function pause() public requireCollectionRole(CollectionRoles.Owner) {
_pause(); _pause();
} }
@ -628,7 +659,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
* - the contract must be paused. * - the contract must be paused.
* *
*/ */
function unpause() public requireCollectionRole(Roles.Controller) { function unpause() public requireCollectionRole(CollectionRoles.Owner) {
_unpause(); _unpause();
} }
@ -641,7 +672,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
* - the contract must be in the oposite pausable state. * - the contract must be in the oposite pausable state.
* *
*/ */
function setPausable(bool pausable) public requireCollectionRole(Roles.Owner) { function setPausable(bool pausable) public requireCollectionRole(CollectionRoles.Owner) {
_setPausable(pausable); _setPausable(pausable);
} }
} }

View File

@ -3,12 +3,21 @@
pragma solidity ^0.8.17; pragma solidity ^0.8.17;
import "./TestBase.sol"; import "./TestBase.sol";
import {FleekAccessControl} from "contracts/FleekAccessControl.sol"; import "contracts/FleekAccessControl.sol";
contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base { contract Test_FleekERC721_AccessControlAssertions is Test {
function expectRevertWithMustHaveAtLeastOneOwner() internal {
vm.expectRevert(abi.encodeWithSelector(MustHaveAtLeastOneOwner.selector));
}
function expectRevertWithRoleAlreadySet() internal {
vm.expectRevert(abi.encodeWithSelector(RoleAlreadySet.selector));
}
}
contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC721_AccessControlAssertions {
uint256 internal tokenId; uint256 internal tokenId;
address internal collectionOwner = address(1); address internal collectionOwner = address(1);
address internal collectionController = address(2);
address internal tokenOwner = address(3); address internal tokenOwner = address(3);
address internal tokenController = address(4); address internal tokenController = address(4);
address internal anyAddress = address(5); address internal anyAddress = address(5);
@ -17,42 +26,31 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
baseSetUp(); baseSetUp();
// Set collectionOwner // Set collectionOwner
CuT.grantCollectionRole(FleekAccessControl.Roles.Owner, collectionOwner); CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);
// Set collectionController
CuT.grantCollectionRole(FleekAccessControl.Roles.Controller, collectionController);
// Mint to tokenOwner to set tokenOwner // Mint to tokenOwner to set tokenOwner
mintDefault(tokenOwner); mintDefault(tokenOwner);
// Set tokenController to minted token // Set tokenController to minted token
vm.prank(tokenOwner); vm.prank(tokenOwner);
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, tokenController); CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenController);
} }
function test_setUp() public { function test_setUp() public {
// Check collectionOwner // Check collectionOwner
assertTrue(CuT.hasCollectionRole(FleekAccessControl.Roles.Owner, collectionOwner)); assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner));
assertFalse(CuT.hasCollectionRole(FleekAccessControl.Roles.Controller, collectionOwner)); assertFalse(CuT.ownerOf(tokenId) == collectionOwner);
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Owner, collectionOwner)); assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, collectionOwner));
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Controller, collectionOwner));
// Check collectionController
assertFalse(CuT.hasCollectionRole(FleekAccessControl.Roles.Owner, collectionController));
assertTrue(CuT.hasCollectionRole(FleekAccessControl.Roles.Controller, collectionController));
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Owner, collectionController));
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Controller, collectionController));
// Check tokenOwner // Check tokenOwner
assertFalse(CuT.hasCollectionRole(FleekAccessControl.Roles.Owner, tokenOwner)); assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, tokenOwner));
assertFalse(CuT.hasCollectionRole(FleekAccessControl.Roles.Controller, tokenOwner)); assertTrue(CuT.ownerOf(tokenId) == tokenOwner);
assertTrue(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Owner, tokenOwner)); assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenOwner));
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Controller, tokenOwner));
// Check tokenController // Check tokenController
assertFalse(CuT.hasCollectionRole(FleekAccessControl.Roles.Owner, tokenController)); assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, tokenController));
assertFalse(CuT.hasCollectionRole(FleekAccessControl.Roles.Controller, tokenController)); assertFalse(CuT.ownerOf(tokenId) == tokenController);
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Owner, tokenController)); assertTrue(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenController));
assertTrue(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Controller, tokenController));
// Check anyAddress // Check anyAddress
assertFalse(CuT.hasCollectionRole(FleekAccessControl.Roles.Owner, anyAddress)); assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, anyAddress));
assertFalse(CuT.hasCollectionRole(FleekAccessControl.Roles.Controller, anyAddress)); assertFalse(CuT.ownerOf(tokenId) == anyAddress);
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Owner, anyAddress)); assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, anyAddress));
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Controller, anyAddress));
} }
function test_grantAndRevokeCollectionRole() public { function test_grantAndRevokeCollectionRole() public {
@ -60,42 +58,34 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// CollectionOwner // CollectionOwner
vm.startPrank(collectionOwner); vm.startPrank(collectionOwner);
CuT.grantCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
assertTrue(CuT.hasCollectionRole(FleekAccessControl.Roles.Controller, randomAddress)); assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress));
CuT.revokeCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
assertFalse(CuT.hasCollectionRole(FleekAccessControl.Roles.Controller, randomAddress)); assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress));
vm.stopPrank();
// CollectionController
vm.startPrank(collectionController);
expectRevertWithCollectionRole();
CuT.grantCollectionRole(FleekAccessControl.Roles.Controller, randomAddress);
expectRevertWithCollectionRole();
CuT.revokeCollectionRole(FleekAccessControl.Roles.Controller, randomAddress);
vm.stopPrank(); vm.stopPrank();
// TokenOwner // TokenOwner
vm.startPrank(tokenOwner); vm.startPrank(tokenOwner);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.grantCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.revokeCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
vm.stopPrank(); vm.stopPrank();
// TokenController // TokenController
vm.startPrank(tokenController); vm.startPrank(tokenController);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.grantCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.revokeCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
vm.stopPrank(); vm.stopPrank();
// AnyAddress // AnyAddress
vm.startPrank(anyAddress); vm.startPrank(anyAddress);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.grantCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.revokeCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
vm.stopPrank(); vm.stopPrank();
} }
@ -104,42 +94,34 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// CollectionOwner // CollectionOwner
vm.startPrank(collectionOwner); vm.startPrank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.revokeTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
vm.stopPrank();
// CollectionController
vm.startPrank(collectionController);
expectRevertWithTokenRole();
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress);
expectRevertWithTokenRole();
CuT.revokeTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress);
vm.stopPrank(); vm.stopPrank();
// TokenOwner // TokenOwner
vm.startPrank(tokenOwner); vm.startPrank(tokenOwner);
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
assertTrue(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress)); assertTrue(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress));
CuT.revokeTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress)); assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress));
vm.stopPrank(); vm.stopPrank();
// TokenController // TokenController
vm.startPrank(tokenController); vm.startPrank(tokenController);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.revokeTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
vm.stopPrank(); vm.stopPrank();
// AnyAddress // AnyAddress
vm.startPrank(anyAddress); vm.startPrank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.revokeTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
vm.stopPrank(); vm.stopPrank();
} }
@ -151,27 +133,21 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
mintDefault(randomAddress); mintDefault(randomAddress);
vm.stopPrank(); vm.stopPrank();
// CollectionController
vm.startPrank(collectionController);
expectRevertWithCollectionRole();
mintDefault(randomAddress);
vm.stopPrank();
// TokenOwner // TokenOwner
vm.startPrank(tokenOwner); vm.startPrank(tokenOwner);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
mintDefault(randomAddress); mintDefault(randomAddress);
vm.stopPrank(); vm.stopPrank();
// TokenController // TokenController
vm.startPrank(tokenController); vm.startPrank(tokenController);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
mintDefault(randomAddress); mintDefault(randomAddress);
vm.stopPrank(); vm.stopPrank();
// AnyAddress // AnyAddress
vm.startPrank(anyAddress); vm.startPrank(anyAddress);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
mintDefault(randomAddress); mintDefault(randomAddress);
vm.stopPrank(); vm.stopPrank();
} }
@ -187,12 +163,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// ColletionOwner // ColletionOwner
vm.prank(collectionOwner); vm.prank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenExternalURL(tokenId, externalURL);
// CollectionController
vm.prank(collectionController);
expectRevertWithTokenRole();
CuT.setTokenExternalURL(tokenId, externalURL); CuT.setTokenExternalURL(tokenId, externalURL);
// TokenOwner // TokenOwner
@ -205,7 +176,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenExternalURL(tokenId, externalURL); CuT.setTokenExternalURL(tokenId, externalURL);
} }
@ -214,12 +185,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// ColletionOwner // ColletionOwner
vm.prank(collectionOwner); vm.prank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenENS(tokenId, ens);
// CollectionController
vm.prank(collectionController);
expectRevertWithTokenRole();
CuT.setTokenENS(tokenId, ens); CuT.setTokenENS(tokenId, ens);
// TokenOwner // TokenOwner
@ -232,7 +198,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenENS(tokenId, ens); CuT.setTokenENS(tokenId, ens);
} }
@ -241,12 +207,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// ColletionOwner // ColletionOwner
vm.prank(collectionOwner); vm.prank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenName(tokenId, name);
// CollectionController
vm.prank(collectionController);
expectRevertWithTokenRole();
CuT.setTokenName(tokenId, name); CuT.setTokenName(tokenId, name);
// TokenOwner // TokenOwner
@ -259,7 +220,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenName(tokenId, name); CuT.setTokenName(tokenId, name);
} }
@ -268,12 +229,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// ColletionOwner // ColletionOwner
vm.prank(collectionOwner); vm.prank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenDescription(tokenId, description);
// CollectionController
vm.prank(collectionController);
expectRevertWithTokenRole();
CuT.setTokenDescription(tokenId, description); CuT.setTokenDescription(tokenId, description);
// TokenOwner // TokenOwner
@ -286,7 +242,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenDescription(tokenId, description); CuT.setTokenDescription(tokenId, description);
} }
@ -295,12 +251,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// ColletionOwner // ColletionOwner
vm.prank(collectionOwner); vm.prank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenLogo(tokenId, logo);
// CollectionController
vm.prank(collectionController);
expectRevertWithTokenRole();
CuT.setTokenLogo(tokenId, logo); CuT.setTokenLogo(tokenId, logo);
// TokenOwner // TokenOwner
@ -313,7 +264,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenLogo(tokenId, logo); CuT.setTokenLogo(tokenId, logo);
} }
@ -322,12 +273,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// ColletionOwner // ColletionOwner
vm.prank(collectionOwner); vm.prank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenColor(tokenId, color);
// CollectionController
vm.prank(collectionController);
expectRevertWithTokenRole();
CuT.setTokenColor(tokenId, color); CuT.setTokenColor(tokenId, color);
// TokenOwner // TokenOwner
@ -340,7 +286,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenColor(tokenId, color); CuT.setTokenColor(tokenId, color);
} }
@ -350,12 +296,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// ColletionOwner // ColletionOwner
vm.prank(collectionOwner); vm.prank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenLogoAndColor(tokenId, logo, color);
// CollectionController
vm.prank(collectionController);
expectRevertWithTokenRole();
CuT.setTokenLogoAndColor(tokenId, logo, color); CuT.setTokenLogoAndColor(tokenId, logo, color);
// TokenOwner // TokenOwner
@ -368,7 +309,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenLogoAndColor(tokenId, logo, color); CuT.setTokenLogoAndColor(tokenId, logo, color);
} }
@ -378,12 +319,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// ColletionOwner // ColletionOwner
vm.prank(collectionOwner); vm.prank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
// CollectionController
vm.prank(collectionController);
expectRevertWithTokenRole();
CuT.setTokenBuild(tokenId, commitHash, gitRepository); CuT.setTokenBuild(tokenId, commitHash, gitRepository);
// TokenOwner // TokenOwner
@ -396,29 +332,24 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setTokenBuild(tokenId, commitHash, gitRepository); CuT.setTokenBuild(tokenId, commitHash, gitRepository);
} }
function test_burn() public { function test_burn() public {
// ColletionOwner // ColletionOwner
vm.prank(collectionOwner); vm.prank(collectionOwner);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.burn(tokenId);
// CollectionController
vm.prank(collectionController);
expectRevertWithTokenRole();
CuT.burn(tokenId); CuT.burn(tokenId);
// TokenController // TokenController
vm.prank(tokenController); vm.prank(tokenController);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.burn(tokenId); CuT.burn(tokenId);
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.burn(tokenId); CuT.burn(tokenId);
// TokenOwner // TokenOwner
@ -433,33 +364,27 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
CuT.unpause(); CuT.unpause();
vm.stopPrank(); vm.stopPrank();
// CollectionController
vm.startPrank(collectionController);
CuT.pause();
CuT.unpause();
vm.stopPrank();
// TokenOwner // TokenOwner
vm.startPrank(tokenOwner); vm.startPrank(tokenOwner);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.pause(); CuT.pause();
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.unpause(); CuT.unpause();
vm.stopPrank(); vm.stopPrank();
// TokenController // TokenController
vm.startPrank(tokenController); vm.startPrank(tokenController);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.pause(); CuT.pause();
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.unpause(); CuT.unpause();
vm.stopPrank(); vm.stopPrank();
// AnyAddress // AnyAddress
vm.startPrank(anyAddress); vm.startPrank(anyAddress);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.pause(); CuT.pause();
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.unpause(); CuT.unpause();
vm.stopPrank(); vm.stopPrank();
} }
@ -469,24 +394,43 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
vm.prank(collectionOwner); vm.prank(collectionOwner);
CuT.setPausable(false); CuT.setPausable(false);
// CollectionController
vm.prank(collectionController);
expectRevertWithCollectionRole();
CuT.setPausable(true);
// TokenOwner // TokenOwner
vm.prank(tokenOwner); vm.prank(tokenOwner);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.setPausable(true); CuT.setPausable(true);
// TokenController // TokenController
vm.prank(tokenController); vm.prank(tokenController);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.setPausable(true); CuT.setPausable(true);
// AnyAddress // AnyAddress
vm.prank(anyAddress); vm.prank(anyAddress);
expectRevertWithCollectionRole(); expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
CuT.setPausable(true); CuT.setPausable(true);
} }
function test_cannotHaveLessThanOneCollectionOwner() public {
CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);
expectRevertWithMustHaveAtLeastOneOwner();
CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, deployer);
}
function test_cannotGrantRoleAlreadyGaranted() public {
expectRevertWithRoleAlreadySet();
CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);
expectRevertWithRoleAlreadySet();
vm.prank(tokenOwner);
CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenController);
}
function test_cannotRevokeRoleAlreadyRevoked() public {
expectRevertWithRoleAlreadySet();
CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, anyAddress);
expectRevertWithRoleAlreadySet();
vm.prank(tokenOwner);
CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, anyAddress);
}
} }

View File

@ -109,13 +109,13 @@ contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base {
CuT.addAccessPoint(tokenId, accessPointName); CuT.addAccessPoint(tokenId, accessPointName);
vm.startPrank(randomAddress); vm.startPrank(randomAddress);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setAccessPointNameVerify(accessPointName, true); CuT.setAccessPointNameVerify(accessPointName, true);
expectRevertWithTokenRole(); expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setAccessPointContentVerify(accessPointName, true); CuT.setAccessPointContentVerify(accessPointName, true);
vm.stopPrank(); vm.stopPrank();
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
vm.startPrank(randomAddress); vm.startPrank(randomAddress);
CuT.setAccessPointNameVerify(accessPointName, true); CuT.setAccessPointNameVerify(accessPointName, true);

View File

@ -20,21 +20,21 @@ contract Test_FleekERC721_Burn is Test_FleekERC721_Base {
function testFuzz_cannotBurnAsNotOwner(address account) public { function testFuzz_cannotBurnAsNotOwner(address account) public {
vm.assume(account != deployer); vm.assume(account != deployer);
vm.prank(account); vm.prank(account);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.burn(tokenId); CuT.burn(tokenId);
} }
function testFuzz_cannotBurnAsController(address account) public { function testFuzz_cannotBurnAsController(address account) public {
vm.assume(account != deployer); vm.assume(account != deployer);
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, account); CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, account);
vm.prank(account); vm.prank(account);
expectRevertWithTokenRole(); expectRevertWithMustBeTokenOwner(tokenId);
CuT.burn(tokenId); CuT.burn(tokenId);
} }
function testFuzz_cannotBurnInexistentToken(uint256 _tokenId) public { function testFuzz_cannotBurnInexistentToken(uint256 _tokenId) public {
vm.assume(_tokenId != tokenId); vm.assume(_tokenId != tokenId);
expectRevertWithTokenRole(); // Token role is tested first before if token exists expectRevertWithInvalidTokenId();
CuT.burn(_tokenId); CuT.burn(_tokenId);
} }
} }

View File

@ -19,7 +19,7 @@ contract Test_FleekERC721_Deploy is Test_FleekERC721_Base {
} }
function test_deployerShouldBeCollectionOwner() public { function test_deployerShouldBeCollectionOwner() public {
assertTrue(CuT.hasCollectionRole(FleekAccessControl.Roles.Owner, deployer)); assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, deployer));
} }
function testFuzz_nameAndSymbol(string memory _name, string memory _symbol) public { function testFuzz_nameAndSymbol(string memory _name, string memory _symbol) public {

View File

@ -106,15 +106,15 @@ contract Test_FleekERC721_Pausable is Test_FleekERC721_Base, Test_FleekERC721_Pa
CuT.removeAccessPoint("accesspoint.com"); CuT.removeAccessPoint("accesspoint.com");
expectRevertWithContractIsPaused(); expectRevertWithContractIsPaused();
CuT.grantCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
expectRevertWithContractIsPaused(); expectRevertWithContractIsPaused();
CuT.revokeCollectionRole(FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
expectRevertWithContractIsPaused(); expectRevertWithContractIsPaused();
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
expectRevertWithContractIsPaused(); expectRevertWithContractIsPaused();
CuT.revokeTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress); CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
} }
} }

View File

@ -3,16 +3,20 @@
pragma solidity ^0.8.17; pragma solidity ^0.8.17;
import "forge-std/Test.sol"; import "forge-std/Test.sol";
import {FleekERC721} from "contracts/FleekERC721.sol"; import "contracts/FleekERC721.sol";
import {TestConstants} from "./Constants.sol"; import {TestConstants} from "./Constants.sol";
abstract contract Test_FleekERC721_Assertions is Test { abstract contract Test_FleekERC721_Assertions is Test {
function expectRevertWithTokenRole() public { function expectRevertWithTokenRole(uint256 tokenId, FleekAccessControl.TokenRoles role) public {
vm.expectRevert("FleekAccessControl: must have token role"); vm.expectRevert(abi.encodeWithSelector(MustHaveTokenRole.selector, tokenId, uint8(role)));
} }
function expectRevertWithCollectionRole() public { function expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles role) public {
vm.expectRevert("FleekAccessControl: must have collection role"); vm.expectRevert(abi.encodeWithSelector(MustHaveCollectionRole.selector, uint8(role)));
}
function expectRevertWithMustBeTokenOwner(uint256 tokenId) public {
vm.expectRevert(abi.encodeWithSelector(MustBeTokenOwner.selector, tokenId));
} }
function expectRevertWithAPAlreadyExists() public { function expectRevertWithAPAlreadyExists() public {

View File

@ -1,8 +1,8 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai'; import { expect } from 'chai';
import { TestConstants, Fixtures } from './helpers'; import { TestConstants, Fixtures, Errors } from './helpers';
const { Roles } = TestConstants; const { CollectionRoles } = TestConstants;
describe('FleekERC721.CollectionRoles', () => { describe('FleekERC721.CollectionRoles', () => {
let fixture: Awaited<ReturnType<typeof Fixtures.default>>; let fixture: Awaited<ReturnType<typeof Fixtures.default>>;
@ -14,77 +14,72 @@ describe('FleekERC721.CollectionRoles', () => {
it('should assign the owner of the contract on contract creation', async () => { it('should assign the owner of the contract on contract creation', async () => {
const { owner, contract } = fixture; const { owner, contract } = fixture;
expect(await contract.hasCollectionRole(Roles.Owner, owner.address)).to.be expect(
.true; await contract.hasCollectionRole(CollectionRoles.Owner, owner.address)
).to.be.true;
}); });
it('should assign owner role to address', async () => { it('should assign owner role to address', async () => {
const { otherAccount, contract } = fixture; const { otherAccount, contract } = fixture;
await contract.grantCollectionRole(Roles.Owner, otherAccount.address); await contract.grantCollectionRole(
CollectionRoles.Owner,
otherAccount.address
);
expect(await contract.hasCollectionRole(Roles.Owner, otherAccount.address)) expect(
.to.be.true; await contract.hasCollectionRole(
}); CollectionRoles.Owner,
otherAccount.address
it('should assign controller role to address', async () => { )
const { owner, contract } = fixture; ).to.be.true;
await contract.grantCollectionRole(Roles.Controller, owner.address);
expect(await contract.hasCollectionRole(Roles.Controller, owner.address)).to
.be.true;
}); });
it('should remove an assigned controller', async () => { it('should remove an assigned controller', async () => {
const { otherAccount, contract } = fixture; const { otherAccount, contract } = fixture;
await contract.grantCollectionRole(Roles.Owner, otherAccount.address);
await contract.revokeCollectionRole(Roles.Owner, otherAccount.address);
expect(await contract.hasCollectionRole(Roles.Owner, otherAccount.address))
.to.be.false;
});
it('should remove an assigned controller', async () => {
const { owner, contract } = fixture;
await contract.grantCollectionRole(Roles.Controller, owner.address);
await contract.revokeCollectionRole(Roles.Controller, owner.address);
expect(await contract.hasCollectionRole(Roles.Controller, owner.address)).to
.be.false;
});
it('should fetch the list of controllers', async () => {
const { owner, contract } = fixture;
await contract.grantCollectionRole(Roles.Controller, owner.address);
await contract.grantCollectionRole( await contract.grantCollectionRole(
Roles.Controller, CollectionRoles.Owner,
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049' otherAccount.address
);
await contract.revokeCollectionRole(
CollectionRoles.Owner,
otherAccount.address
); );
expect(await contract.getCollectionRoleMembers(Roles.Controller)).to.eql([ expect(
owner.address, await contract.hasCollectionRole(
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', CollectionRoles.Owner,
]); otherAccount.address
)
).to.be.false;
}); });
it('should fetch the list of owners', async () => { it('should fetch the list of owners', async () => {
const { owner, contract, otherAccount } = fixture; const { owner, contract, otherAccount } = fixture;
await contract.grantCollectionRole(Roles.Owner, otherAccount.address);
await contract.grantCollectionRole( await contract.grantCollectionRole(
Roles.Owner, CollectionRoles.Owner,
otherAccount.address
);
await contract.grantCollectionRole(
CollectionRoles.Owner,
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049' '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049'
); );
expect(await contract.getCollectionRoleMembers(Roles.Owner)).to.eql([ expect(
owner.address, await contract.hasCollectionRole(
otherAccount.address, CollectionRoles.Owner,
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', otherAccount.address
]); )
).to.be.true;
expect(
await contract.hasCollectionRole(
CollectionRoles.Owner,
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049'
)
).to.be.true;
}); });
it('should not be able to add new owner', async () => { it('should not be able to add new owner', async () => {
@ -93,73 +88,91 @@ describe('FleekERC721.CollectionRoles', () => {
await expect( await expect(
contract contract
.connect(otherAccount) .connect(otherAccount)
.grantCollectionRole(Roles.Owner, otherAccount.address) .grantCollectionRole(CollectionRoles.Owner, otherAccount.address)
).to.be.revertedWith('FleekAccessControl: must have collection role'); )
}); .to.be.revertedWithCustomError(contract, Errors.MustHaveCollectionRole)
.withArgs(CollectionRoles.Owner);
it('should not be able to add new controller', async () => {
const { otherAccount, contract } = fixture;
await expect(
contract
.connect(otherAccount)
.grantCollectionRole(Roles.Controller, otherAccount.address)
).to.be.revertedWith('FleekAccessControl: must have collection role');
}); });
it('should be able to add roles after owner being granted', async () => { it('should be able to add roles after owner being granted', async () => {
const { otherAccount, contract } = fixture; const { otherAccount, contract } = fixture;
await contract.grantCollectionRole(Roles.Owner, otherAccount.address); await contract.grantCollectionRole(
CollectionRoles.Owner,
otherAccount.address
);
await expect( await expect(
contract contract
.connect(otherAccount) .connect(otherAccount)
.grantCollectionRole(Roles.Controller, otherAccount.address) .grantCollectionRole(
CollectionRoles.Owner,
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049'
)
).to.not.be.reverted; ).to.not.be.reverted;
await expect(
contract
.connect(otherAccount)
.revokeCollectionRole(Roles.Controller, otherAccount.address)
).to.not.be.reverted;
});
it('should not be able to change roles for controllers', async () => {
const { owner, otherAccount, contract } = fixture;
await contract.grantCollectionRole(Roles.Controller, otherAccount.address);
await expect(
contract
.connect(otherAccount)
.grantCollectionRole(Roles.Owner, owner.address)
).to.be.revertedWith('FleekAccessControl: must have collection role');
await expect(
contract
.connect(otherAccount)
.revokeCollectionRole(Roles.Owner, owner.address)
).to.be.revertedWith('FleekAccessControl: must have collection role');
}); });
it('should emit event when role is granted', async () => { it('should emit event when role is granted', async () => {
const { owner, contract, otherAccount } = fixture; const { owner, contract, otherAccount } = fixture;
await expect( await expect(
contract.grantCollectionRole(Roles.Controller, otherAccount.address) contract.grantCollectionRole(CollectionRoles.Owner, otherAccount.address)
) )
.to.emit(contract, 'CollectionRoleGranted') .to.emit(contract, 'CollectionRoleChanged')
.withArgs(Roles.Controller, otherAccount.address, owner.address); .withArgs(
CollectionRoles.Owner,
otherAccount.address,
true,
owner.address
);
}); });
it('should emit event when role is revoked', async () => { it('should emit event when role is revoked', async () => {
const { owner, contract, otherAccount } = fixture; const { owner, contract, otherAccount } = fixture;
await contract.grantCollectionRole(Roles.Controller, otherAccount.address); await contract.grantCollectionRole(
CollectionRoles.Owner,
otherAccount.address
);
await expect( await expect(
contract.revokeCollectionRole(Roles.Controller, otherAccount.address) contract.revokeCollectionRole(CollectionRoles.Owner, otherAccount.address)
) )
.to.emit(contract, 'CollectionRoleRevoked') .to.emit(contract, 'CollectionRoleChanged')
.withArgs(Roles.Controller, otherAccount.address, owner.address); .withArgs(
CollectionRoles.Owner,
otherAccount.address,
false,
owner.address
);
});
it('should not be able to grant role if already granted', async () => {
const { otherAccount, contract } = fixture;
await contract.grantCollectionRole(
CollectionRoles.Owner,
otherAccount.address
);
await expect(
contract.grantCollectionRole(CollectionRoles.Owner, otherAccount.address)
).to.be.revertedWithCustomError(contract, Errors.RoleAlreadySet);
});
it('should not be able to revoke role if not granted', async () => {
const { otherAccount, contract } = fixture;
await expect(
contract.revokeCollectionRole(CollectionRoles.Owner, otherAccount.address)
).to.be.revertedWithCustomError(contract, Errors.RoleAlreadySet);
});
it('should not be able to remove all collection owners', async () => {
const { owner, contract } = fixture;
await expect(
contract.revokeCollectionRole(CollectionRoles.Owner, owner.address)
).to.be.revertedWithCustomError(contract, Errors.MustHaveAtLeastOneOwner);
}); });
}); });

View File

@ -1,7 +1,9 @@
export const TestConstants = Object.freeze({ export const TestConstants = Object.freeze({
Roles: { CollectionRoles: {
Owner: 0, Owner: 0,
Controller: 1, },
TokenRoles: {
Controller: 0,
}, },
MintParams: { MintParams: {
name: 'Fleek Test App', name: 'Fleek Test App',

View File

@ -1,4 +1,9 @@
export const Errors = Object.freeze({ export const Errors = Object.freeze({
MustHaveCollectionRole: 'MustHaveCollectionRole',
MustHaveTokenRole: 'MustHaveTokenRole',
MustHaveAtLeastOneOwner: 'MustHaveAtLeastOneOwner',
RoleAlreadySet: 'RoleAlreadySet',
MustBeTokenOwner: 'MustBeTokenOwner',
ContractIsPaused: 'ContractIsPaused', ContractIsPaused: 'ContractIsPaused',
ContractIsNotPaused: 'ContractIsNotPaused', ContractIsNotPaused: 'ContractIsNotPaused',
ContractIsNotPausable: 'ContractIsNotPausable', ContractIsNotPausable: 'ContractIsNotPausable',

View File

@ -1,9 +1,9 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai'; import { expect } from 'chai';
import { TestConstants, Fixtures } from './helpers'; import { TestConstants, Fixtures, Errors } from './helpers';
import { ethers } from 'hardhat'; import { ethers } from 'hardhat';
const { MintParams, Roles } = TestConstants; const { MintParams, CollectionRoles } = TestConstants;
describe('FleekERC721.Minting', () => { describe('FleekERC721.Minting', () => {
it('should be able to mint a new token', async () => { it('should be able to mint a new token', async () => {
@ -42,7 +42,9 @@ describe('FleekERC721.Minting', () => {
MintParams.logo, MintParams.logo,
MintParams.color MintParams.color
) )
).to.be.revertedWith('FleekAccessControl: must have collection role'); )
.to.be.revertedWithCustomError(contract, Errors.MustHaveCollectionRole)
.withArgs(CollectionRoles.Owner);
}); });
it('should have address to as owner', async () => { it('should have address to as owner', async () => {
@ -65,12 +67,6 @@ describe('FleekERC721.Minting', () => {
const tokenId = response.value.toNumber(); const tokenId = response.value.toNumber();
expect(await contract.ownerOf(tokenId)).to.equal(owner.address); expect(await contract.ownerOf(tokenId)).to.equal(owner.address);
expect(await contract.hasTokenRole(tokenId, Roles.Owner, owner.address)).to
.be.true;
expect(await contract.ownerOf(tokenId)).not.to.equal(otherAccount.address); expect(await contract.ownerOf(tokenId)).not.to.equal(otherAccount.address);
expect(
await contract.hasTokenRole(tokenId, Roles.Owner, otherAccount.address)
).to.be.false;
}); });
}); });

View File

@ -2,7 +2,7 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai'; import { expect } from 'chai';
import { TestConstants, Fixtures, Errors } from './helpers'; import { TestConstants, Fixtures, Errors } from './helpers';
const { MintParams, Roles } = TestConstants; const { MintParams, CollectionRoles, TokenRoles } = TestConstants;
describe('FleekERC721.Pausable', () => { describe('FleekERC721.Pausable', () => {
let fixture: Awaited<ReturnType<typeof Fixtures.default>>; let fixture: Awaited<ReturnType<typeof Fixtures.default>>;
@ -134,19 +134,27 @@ describe('FleekERC721.Pausable', () => {
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused); ).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
await expect( await expect(
contract.grantCollectionRole(Roles.Controller, otherAccount.address) contract.grantCollectionRole(CollectionRoles.Owner, otherAccount.address)
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused); ).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
await expect( await expect(
contract.revokeCollectionRole(Roles.Controller, otherAccount.address) contract.revokeCollectionRole(CollectionRoles.Owner, otherAccount.address)
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused); ).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
await expect( await expect(
contract.grantTokenRole(Roles.Controller, tokenId, otherAccount.address) contract.grantTokenRole(
CollectionRoles.Owner,
tokenId,
otherAccount.address
)
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused); ).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
await expect( await expect(
contract.revokeTokenRole(Roles.Controller, tokenId, otherAccount.address) contract.revokeTokenRole(
CollectionRoles.Owner,
tokenId,
otherAccount.address
)
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused); ).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
}); });
}); });

View File

@ -1,8 +1,8 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai'; import { expect } from 'chai';
import { TestConstants, Fixtures, parseTokenURI } from './helpers'; import { TestConstants, Fixtures, parseTokenURI, Errors } from './helpers';
const { Roles } = TestConstants; const { TokenRoles } = TestConstants;
describe('FleekERC721.TokenRoles', () => { describe('FleekERC721.TokenRoles', () => {
let fixture: Awaited<ReturnType<typeof Fixtures.withMint>>; let fixture: Awaited<ReturnType<typeof Fixtures.withMint>>;
@ -19,27 +19,23 @@ describe('FleekERC721.TokenRoles', () => {
it('should match the owner role for minter', async () => { it('should match the owner role for minter', async () => {
const { contract, owner, tokenId } = fixture; const { contract, owner, tokenId } = fixture;
const hasRole = await contract.hasTokenRole( const tokenOwner = await contract.ownerOf(tokenId);
tokenId,
Roles.Owner,
owner.address
);
expect(hasRole).to.be.true; expect(tokenOwner).to.be.equal(owner.address);
}); });
it('should add a new controller', async () => { it('should add a new controller', async () => {
const { contract, owner, otherAccount, tokenId } = fixture; const { contract, owner, otherAccount, tokenId } = fixture;
await contract.grantTokenRole( await contract.grantTokenRole(
tokenId, tokenId,
Roles.Controller, TokenRoles.Controller,
otherAccount.address otherAccount.address
); );
expect( expect(
await contract.hasTokenRole( await contract.hasTokenRole(
tokenId, tokenId,
Roles.Controller, TokenRoles.Controller,
otherAccount.address otherAccount.address
) )
).to.be.true; ).to.be.true;
@ -49,71 +45,55 @@ describe('FleekERC721.TokenRoles', () => {
const { contract, tokenId } = fixture; const { contract, tokenId } = fixture;
await contract.grantTokenRole( await contract.grantTokenRole(
tokenId, tokenId,
Roles.Controller, TokenRoles.Controller,
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049' '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049'
); );
await contract.grantTokenRole( await contract.grantTokenRole(
tokenId, tokenId,
Roles.Controller, TokenRoles.Controller,
'0x2FEd6Ef3c495922263B403319FA6DDB323DD49E3' '0x2FEd6Ef3c495922263B403319FA6DDB323DD49E3'
); );
expect( expect(
await contract.getTokenRoleMembers(tokenId, Roles.Controller) await contract.hasTokenRole(
).to.eql([ tokenId,
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', TokenRoles.Controller,
'0x2FEd6Ef3c495922263B403319FA6DDB323DD49E3', '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049'
]); )
}); ).to.be.true;
expect(
it('should add a list of owners', async () => { await contract.hasTokenRole(
const { contract, owner, tokenId } = fixture; tokenId,
await contract.grantTokenRole( TokenRoles.Controller,
tokenId, '0x2FEd6Ef3c495922263B403319FA6DDB323DD49E3'
Roles.Owner, )
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049' ).to.be.true;
);
await contract.grantTokenRole(
tokenId,
Roles.Owner,
'0x2FEd6Ef3c495922263B403319FA6DDB323DD49E3'
);
expect(await contract.getTokenRoleMembers(tokenId, Roles.Owner)).to.eql([
owner.address,
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049',
'0x2FEd6Ef3c495922263B403319FA6DDB323DD49E3',
]);
}); });
it('should not match the owner role for other account', async () => { it('should not match the owner role for other account', async () => {
const { contract, otherAccount, tokenId } = fixture; const { contract, otherAccount, tokenId } = fixture;
const hasRole = await contract.hasTokenRole( const tokenOwner = await contract.ownerOf(tokenId);
tokenId,
Roles.Owner,
otherAccount.address
);
expect(hasRole).to.be.false; expect(tokenOwner).to.not.be.equal(otherAccount.address);
}); });
it('should remove an added controller', async () => { it('should remove an added controller', async () => {
const { contract, owner, otherAccount, tokenId } = fixture; const { contract, owner, otherAccount, tokenId } = fixture;
await contract.grantTokenRole( await contract.grantTokenRole(
tokenId, tokenId,
Roles.Controller, TokenRoles.Controller,
otherAccount.address otherAccount.address
); );
await contract.revokeTokenRole( await contract.revokeTokenRole(
tokenId, tokenId,
Roles.Controller, TokenRoles.Controller,
otherAccount.address otherAccount.address
); );
expect( expect(
await contract.hasTokenRole( await contract.hasTokenRole(
tokenId, tokenId,
Roles.Controller, TokenRoles.Controller,
otherAccount.address otherAccount.address
) )
).to.be.false; ).to.be.false;
@ -124,86 +104,112 @@ describe('FleekERC721.TokenRoles', () => {
await contract.transferFrom(owner.address, otherAccount.address, tokenId); await contract.transferFrom(owner.address, otherAccount.address, tokenId);
expect(await contract.ownerOf(tokenId)).to.equal(otherAccount.address); expect(await contract.ownerOf(tokenId)).to.equal(otherAccount.address);
expect(
await contract.hasTokenRole(tokenId, Roles.Owner, otherAccount.address)
).to.be.true;
expect(await contract.hasTokenRole(tokenId, Roles.Owner, owner.address)).to
.be.false;
}); });
it('should clean the token controller list after transfer', async () => { it('should clean the token controller list after transfer', async () => {
const { contract, owner, otherAccount, tokenId } = fixture; const { contract, owner, otherAccount, tokenId } = fixture;
await contract.grantTokenRole( await contract.grantTokenRole(
tokenId, tokenId,
Roles.Controller, TokenRoles.Controller,
otherAccount.address otherAccount.address
); );
await contract.transferFrom(owner.address, otherAccount.address, tokenId); await contract.transferFrom(owner.address, otherAccount.address, tokenId);
expect(await contract.getTokenRoleMembers(tokenId, 1)).to.eql([]); expect(
await contract.hasTokenRole(
tokenId,
TokenRoles.Controller,
otherAccount.address
)
).to.be.false;
}); });
it('should not be able to add address role', async () => { it('should not be able to add address role', async () => {
const { contract, owner, otherAccount, tokenId } = fixture; const { contract, otherAccount, tokenId } = fixture;
await expect( await expect(
contract contract
.connect(otherAccount) .connect(otherAccount)
.grantTokenRole(tokenId, Roles.Owner, otherAccount.address) .grantTokenRole(tokenId, TokenRoles.Controller, otherAccount.address)
).to.be.revertedWith('FleekAccessControl: must have token role'); ).to.be.revertedWithCustomError(contract, Errors.MustBeTokenOwner);
await expect(
contract
.connect(otherAccount)
.grantTokenRole(tokenId, Roles.Controller, otherAccount.address)
).to.be.revertedWith('FleekAccessControl: must have token role');
}); });
it('should not be able to remove address role', async () => { it('should not be able to remove address role', async () => {
const { contract, owner, otherAccount, tokenId } = fixture; const { contract, owner, otherAccount, tokenId } = fixture;
await expect(
contract
.connect(otherAccount)
.revokeTokenRole(tokenId, Roles.Owner, otherAccount.address)
).to.be.revertedWith('FleekAccessControl: must have token role');
await expect( await expect(
contract contract
.connect(otherAccount) .connect(otherAccount)
.revokeTokenRole(tokenId, Roles.Controller, otherAccount.address) .revokeTokenRole(tokenId, TokenRoles.Controller, otherAccount.address)
).to.be.revertedWith('FleekAccessControl: must have token role'); ).to.be.revertedWithCustomError(contract, Errors.MustBeTokenOwner);
});
it('should be able to add token role after owner role granted', async () => {
const { contract, owner, otherAccount, tokenId } = fixture;
await contract.grantTokenRole(tokenId, Roles.Owner, otherAccount.address);
expect(
await contract
.connect(otherAccount)
.grantTokenRole(tokenId, Roles.Controller, otherAccount.address)
).to.not.be.reverted;
}); });
it('should emit event when token role is granted', async () => { it('should emit event when token role is granted', async () => {
const { contract, owner, otherAccount, tokenId } = fixture; const { contract, owner, otherAccount, tokenId } = fixture;
await expect( await expect(
contract.grantTokenRole(tokenId, Roles.Controller, otherAccount.address) contract.grantTokenRole(
tokenId,
TokenRoles.Controller,
otherAccount.address
)
) )
.to.emit(contract, 'TokenRoleGranted') .to.emit(contract, 'TokenRoleChanged')
.withArgs(tokenId, Roles.Controller, otherAccount.address, owner.address); .withArgs(
tokenId,
TokenRoles.Controller,
otherAccount.address,
true,
owner.address
);
}); });
it('should emit event when token role is revoked', async () => { it('should emit event when token role is revoked', async () => {
const { contract, owner, otherAccount, tokenId } = fixture; const { contract, owner, otherAccount, tokenId } = fixture;
await contract.grantTokenRole( await contract.grantTokenRole(
tokenId, tokenId,
Roles.Controller, TokenRoles.Controller,
otherAccount.address otherAccount.address
); );
await expect( await expect(
contract.revokeTokenRole(tokenId, Roles.Controller, otherAccount.address) contract.revokeTokenRole(
tokenId,
TokenRoles.Controller,
otherAccount.address
)
) )
.to.emit(contract, 'TokenRoleRevoked') .to.emit(contract, 'TokenRoleChanged')
.withArgs(tokenId, Roles.Controller, otherAccount.address, owner.address); .withArgs(
tokenId,
TokenRoles.Controller,
otherAccount.address,
false,
owner.address
);
});
it('should not be able to grant role twice', async () => {
const { contract, otherAccount, tokenId } = fixture;
await contract.grantTokenRole(
tokenId,
TokenRoles.Controller,
otherAccount.address
);
await expect(
contract.grantTokenRole(
tokenId,
TokenRoles.Controller,
otherAccount.address
)
).to.be.revertedWithCustomError(contract, Errors.RoleAlreadySet);
});
it('should not be able to revoke role twice', async () => {
const { contract, otherAccount, tokenId } = fixture;
await expect(
contract.revokeTokenRole(
tokenId,
TokenRoles.Controller,
otherAccount.address
)
).to.be.revertedWithCustomError(contract, Errors.RoleAlreadySet);
}); });
}); });