feature: make main contract pausable (#110)
* feat: add FleekPausable * feat: add FleekPausable functions in FleekERC721 * fix: requirePaused logic * feat: add pause modifiers in FleekERC721 * refactor: move functions to the main contract to add pause modifier * test: add unpause to test setups * fix: revokeTokenRole modifier * test: add initial tests and setup for pausable * test: all test for pause and pausable states * test: add test for functions when contract is paused * test: add pausable hardhat tests * test: foundry access control test for pausable * refactor: function names * fix: remove virtual keywords for functions that must not be overriden * refactor: set inital state for unpaused
This commit is contained in:
parent
197a7a28c5
commit
0f05a912a7
|
|
@ -60,62 +60,6 @@ contract FleekAccessControl is Initializable {
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Grants the collection role to an address.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
*
|
|
||||||
* - the caller should have the collection role.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function grantCollectionRole(Roles role, address account) public requireCollectionRole(Roles.Owner) {
|
|
||||||
_grantCollectionRole(role, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Grants the token role to an address.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
*
|
|
||||||
* - the caller should have the token role.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function grantTokenRole(
|
|
||||||
uint256 tokenId,
|
|
||||||
Roles role,
|
|
||||||
address account
|
|
||||||
) public requireTokenRole(tokenId, Roles.Owner) {
|
|
||||||
_grantTokenRole(tokenId, role, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Revokes the collection role of an address.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
*
|
|
||||||
* - the caller should have the collection role.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function revokeCollectionRole(Roles role, address account) public requireCollectionRole(Roles.Owner) {
|
|
||||||
_revokeCollectionRole(role, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Revokes the token role of an address.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
*
|
|
||||||
* - the caller should have the token role.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function revokeTokenRole(
|
|
||||||
uint256 tokenId,
|
|
||||||
Roles role,
|
|
||||||
address account
|
|
||||||
) public requireTokenRole(tokenId, Roles.Owner) {
|
|
||||||
_revokeTokenRole(tokenId, role, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Returns `True` if a certain address has the collection role.
|
* @dev Returns `True` if a certain address has the collection role.
|
||||||
*/
|
*/
|
||||||
|
|
@ -228,4 +172,11 @@ contract FleekAccessControl is Initializable {
|
||||||
_clearAllTokenRoles(tokenId);
|
_clearAllTokenRoles(tokenId);
|
||||||
_grantTokenRole(tokenId, Roles.Owner, newOwner);
|
_grantTokenRole(tokenId, Roles.Owner, newOwner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@ import "@openzeppelin/contracts/utils/Base64.sol";
|
||||||
import "@openzeppelin/contracts/utils/Strings.sol";
|
import "@openzeppelin/contracts/utils/Strings.sol";
|
||||||
import "./FleekAccessControl.sol";
|
import "./FleekAccessControl.sol";
|
||||||
import "./util/FleekStrings.sol";
|
import "./util/FleekStrings.sol";
|
||||||
|
import "./FleekPausable.sol";
|
||||||
|
|
||||||
error ThereIsNoTokenMinted();
|
error ThereIsNoTokenMinted();
|
||||||
|
|
||||||
contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl {
|
contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, FleekPausable {
|
||||||
using Strings for uint256;
|
using Strings for uint256;
|
||||||
using Counters for Counters.Counter;
|
using Counters for Counters.Counter;
|
||||||
using FleekStrings for FleekERC721.App;
|
using FleekStrings for FleekERC721.App;
|
||||||
|
|
@ -84,6 +85,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl {
|
||||||
function initialize(string memory _name, string memory _symbol) public initializer {
|
function initialize(string memory _name, string memory _symbol) public initializer {
|
||||||
__ERC721_init(_name, _symbol);
|
__ERC721_init(_name, _symbol);
|
||||||
__FleekAccessControl_init();
|
__FleekAccessControl_init();
|
||||||
|
__FleekPausable_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -102,6 +104,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl {
|
||||||
* Requirements:
|
* Requirements:
|
||||||
*
|
*
|
||||||
* - the caller must have ``collectionOwner``'s admin role.
|
* - the caller must have ``collectionOwner``'s admin role.
|
||||||
|
* - the contract must be not paused.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function mint(
|
function mint(
|
||||||
|
|
@ -200,7 +203,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl {
|
||||||
address to,
|
address to,
|
||||||
uint256 tokenId,
|
uint256 tokenId,
|
||||||
uint256 batchSize
|
uint256 batchSize
|
||||||
) internal virtual override {
|
) internal virtual override whenNotPaused {
|
||||||
if (from != address(0) && to != address(0)) {
|
if (from != address(0) && to != address(0)) {
|
||||||
// Transfer
|
// Transfer
|
||||||
_clearAllTokenRoles(tokenId, to);
|
_clearAllTokenRoles(tokenId, to);
|
||||||
|
|
@ -367,10 +370,11 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl {
|
||||||
* Requirements:
|
* Requirements:
|
||||||
*
|
*
|
||||||
* - the tokenId must be minted and valid.
|
* - the tokenId must be minted and valid.
|
||||||
|
* - the contract must be not paused.
|
||||||
*
|
*
|
||||||
* IMPORTANT: The payment is not set yet
|
* IMPORTANT: The payment is not set yet
|
||||||
*/
|
*/
|
||||||
function addAccessPoint(uint256 tokenId, string memory apName) public payable {
|
function addAccessPoint(uint256 tokenId, string memory apName) public payable whenNotPaused {
|
||||||
// require(msg.value == 0.1 ether, "You need to pay at least 0.1 ETH"); // TODO: define a minimum price
|
// require(msg.value == 0.1 ether, "You need to pay at least 0.1 ETH"); // TODO: define a minimum price
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
require(_accessPoints[apName].owner == address(0), "FleekERC721: AP already exists");
|
require(_accessPoints[apName].owner == address(0), "FleekERC721: AP already exists");
|
||||||
|
|
@ -390,8 +394,10 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl {
|
||||||
*
|
*
|
||||||
* - the AP must exist.
|
* - the AP must exist.
|
||||||
* - must be called by the AP owner.
|
* - must be called by the AP owner.
|
||||||
|
* - the contract must be not paused.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
function removeAccessPoint(string memory apName) public requireAP(apName) {
|
function removeAccessPoint(string memory apName) public whenNotPaused requireAP(apName) {
|
||||||
require(msg.sender == _accessPoints[apName].owner, "FleekERC721: must be AP owner");
|
require(msg.sender == _accessPoints[apName].owner, "FleekERC721: must be AP owner");
|
||||||
uint256 tokenId = _accessPoints[apName].tokenId;
|
uint256 tokenId = _accessPoints[apName].tokenId;
|
||||||
|
|
||||||
|
|
@ -524,6 +530,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl {
|
||||||
*
|
*
|
||||||
* - the tokenId must be minted and valid.
|
* - the tokenId must be minted and valid.
|
||||||
* - the sender must have the `tokenOwner` role.
|
* - the sender must have the `tokenOwner` role.
|
||||||
|
* - the contract must be not paused.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function burn(uint256 tokenId) public virtual requireTokenRole(tokenId, Roles.Owner) {
|
function burn(uint256 tokenId) public virtual requireTokenRole(tokenId, Roles.Owner) {
|
||||||
|
|
@ -533,4 +540,108 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl {
|
||||||
delete _apps[tokenId];
|
delete _apps[tokenId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////
|
||||||
|
ACCESS CONTROL
|
||||||
|
//////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Grants the collection role to an address.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller should have the collection role.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function grantCollectionRole(Roles role, address account) public whenNotPaused requireCollectionRole(Roles.Owner) {
|
||||||
|
_grantCollectionRole(role, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Grants the token role to an address.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller should have the token role.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function grantTokenRole(
|
||||||
|
uint256 tokenId,
|
||||||
|
Roles role,
|
||||||
|
address account
|
||||||
|
) public whenNotPaused requireTokenRole(tokenId, Roles.Owner) {
|
||||||
|
_grantTokenRole(tokenId, role, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Revokes the collection role of an address.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller should have the collection role.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function revokeCollectionRole(Roles role, address account) public whenNotPaused requireCollectionRole(Roles.Owner) {
|
||||||
|
_revokeCollectionRole(role, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Revokes the token role of an address.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller should have the token role.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function revokeTokenRole(
|
||||||
|
uint256 tokenId,
|
||||||
|
Roles role,
|
||||||
|
address account
|
||||||
|
) public whenNotPaused requireTokenRole(tokenId, Roles.Owner) {
|
||||||
|
_revokeTokenRole(tokenId, role, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////
|
||||||
|
PAUSABLE
|
||||||
|
//////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Sets the contract to paused state.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the sender must have the `controller` role.
|
||||||
|
* - the contract must be pausable.
|
||||||
|
* - the contract must be not paused.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function pause() public requireCollectionRole(Roles.Controller) {
|
||||||
|
_pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Sets the contract to unpaused state.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the sender must have the `controller` role.
|
||||||
|
* - the contract must be paused.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function unpause() public requireCollectionRole(Roles.Controller) {
|
||||||
|
_unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Sets the contract to pausable state.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the sender must have the `owner` role.
|
||||||
|
* - the contract must be in the oposite pausable state.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function setPausable(bool pausable) public requireCollectionRole(Roles.Owner) {
|
||||||
|
_setPausable(pausable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.7;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||||
|
|
||||||
|
error ContractIsPaused();
|
||||||
|
error ContractIsNotPaused();
|
||||||
|
error ContractIsNotPausable();
|
||||||
|
error PausableIsSetTo(bool state);
|
||||||
|
|
||||||
|
abstract contract FleekPausable is Initializable {
|
||||||
|
/**
|
||||||
|
* @dev Emitted when the pause is triggered by `account` and set to `isPaused`.
|
||||||
|
*/
|
||||||
|
event PauseStatusChange(bool indexed isPaused, address account);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Emitted when the pausable is triggered by `account` and set to `isPausable`.
|
||||||
|
*/
|
||||||
|
event PausableStatusChange(bool indexed isPausable, address account);
|
||||||
|
|
||||||
|
bool private _paused;
|
||||||
|
bool private _canPause; // TODO: how should we verify if the contract is pausable or not?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Initializes the contract in unpaused state.
|
||||||
|
*/
|
||||||
|
function __FleekPausable_init() internal onlyInitializing {
|
||||||
|
_paused = false;
|
||||||
|
_canPause = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Modifier to make a function callable only when the contract is not paused.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - The contract must not be paused.
|
||||||
|
*/
|
||||||
|
modifier whenNotPaused() {
|
||||||
|
_requireNotPaused();
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Modifier to make a function callable only when the contract is paused.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - The contract must be paused.
|
||||||
|
*/
|
||||||
|
modifier whenPaused() {
|
||||||
|
_requirePaused();
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns true if the contract is paused, and false otherwise.
|
||||||
|
*/
|
||||||
|
function isPaused() public view returns (bool) {
|
||||||
|
return _paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns true if the contract is pausable, and false otherwise.
|
||||||
|
*/
|
||||||
|
function isPausable() public view returns (bool) {
|
||||||
|
return _canPause;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Throws if the contract is paused.
|
||||||
|
*/
|
||||||
|
function _requireNotPaused() internal view {
|
||||||
|
if (isPaused()) revert ContractIsPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Throws if the contract is not paused.
|
||||||
|
*/
|
||||||
|
function _requirePaused() internal view {
|
||||||
|
if (!isPaused()) revert ContractIsNotPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Throws if the contract is not pausable.
|
||||||
|
*/
|
||||||
|
function _requirePausable() internal view {
|
||||||
|
if (!isPausable()) revert ContractIsNotPausable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Sets the contract to be pausable or not.
|
||||||
|
* @param canPause true if the contract is pausable, and false otherwise.
|
||||||
|
*/
|
||||||
|
function _setPausable(bool canPause) internal {
|
||||||
|
if (canPause == _canPause) revert PausableIsSetTo(canPause);
|
||||||
|
_canPause = canPause;
|
||||||
|
emit PausableStatusChange(canPause, msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Triggers stopped state.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - The contract must not be paused.
|
||||||
|
*/
|
||||||
|
function _pause() internal whenNotPaused {
|
||||||
|
_requirePausable();
|
||||||
|
_paused = true;
|
||||||
|
emit PauseStatusChange(false, msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns to normal state.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - The contract must be paused.
|
||||||
|
*/
|
||||||
|
function _unpause() internal whenPaused {
|
||||||
|
_paused = false;
|
||||||
|
emit PauseStatusChange(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
|
||||||
|
*/
|
||||||
|
uint256[49] private __gap;
|
||||||
|
}
|
||||||
|
|
@ -400,7 +400,7 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
|
||||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_testBurn() public {
|
function test_burn() public {
|
||||||
// ColletionOwner
|
// ColletionOwner
|
||||||
vm.prank(collectionOwner);
|
vm.prank(collectionOwner);
|
||||||
expectRevertWithTokenRole();
|
expectRevertWithTokenRole();
|
||||||
|
|
@ -425,4 +425,68 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base {
|
||||||
vm.prank(tokenOwner);
|
vm.prank(tokenOwner);
|
||||||
CuT.burn(tokenId);
|
CuT.burn(tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_pauseAndUnpause() public {
|
||||||
|
// ColletionOwner
|
||||||
|
vm.startPrank(collectionOwner);
|
||||||
|
CuT.pause();
|
||||||
|
CuT.unpause();
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
// CollectionController
|
||||||
|
vm.startPrank(collectionController);
|
||||||
|
CuT.pause();
|
||||||
|
CuT.unpause();
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
// TokenOwner
|
||||||
|
vm.startPrank(tokenOwner);
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.pause();
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.unpause();
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
// TokenController
|
||||||
|
vm.startPrank(tokenController);
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.pause();
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.unpause();
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
// AnyAddress
|
||||||
|
vm.startPrank(anyAddress);
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.pause();
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.unpause();
|
||||||
|
vm.stopPrank();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_setPausable() public {
|
||||||
|
// ColletionOwner
|
||||||
|
vm.prank(collectionOwner);
|
||||||
|
CuT.setPausable(false);
|
||||||
|
|
||||||
|
// CollectionController
|
||||||
|
vm.prank(collectionController);
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.setPausable(true);
|
||||||
|
|
||||||
|
// TokenOwner
|
||||||
|
vm.prank(tokenOwner);
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.setPausable(true);
|
||||||
|
|
||||||
|
// TokenController
|
||||||
|
vm.prank(tokenController);
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.setPausable(true);
|
||||||
|
|
||||||
|
// AnyAddress
|
||||||
|
vm.prank(anyAddress);
|
||||||
|
expectRevertWithCollectionRole();
|
||||||
|
CuT.setPausable(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.17;
|
||||||
|
|
||||||
|
import "./TestBase.sol";
|
||||||
|
import "contracts/FleekPausable.sol";
|
||||||
|
import "contracts/FleekAccessControl.sol";
|
||||||
|
|
||||||
|
contract Test_FleekERC721_PausableAssertions is Test {
|
||||||
|
function expectRevertWithContractIsPaused() public {
|
||||||
|
vm.expectRevert(ContractIsPaused.selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectRevertWithContractIsNotPaused() public {
|
||||||
|
vm.expectRevert(ContractIsNotPaused.selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectRevertWithContractIsNotPausable() public {
|
||||||
|
vm.expectRevert(ContractIsNotPausable.selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectRevertWithPausableIsSetTo(bool value) public {
|
||||||
|
vm.expectRevert(abi.encodeWithSelector(PausableIsSetTo.selector, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract Test_FleekERC721_Pausable is Test_FleekERC721_Base, Test_FleekERC721_PausableAssertions {
|
||||||
|
function setUp() public {
|
||||||
|
baseSetUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_shouldBeInitializedPausedAndPausable() public {
|
||||||
|
assertFalse(CuT.isPaused());
|
||||||
|
assertTrue(CuT.isPausable());
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_shouldUnpause() public {
|
||||||
|
CuT.pause();
|
||||||
|
assertTrue(CuT.isPaused());
|
||||||
|
|
||||||
|
CuT.unpause();
|
||||||
|
assertFalse(CuT.isPaused());
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_shouldPause() public {
|
||||||
|
CuT.pause();
|
||||||
|
assertTrue(CuT.isPaused());
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_cannotPauseWhenAlreadyPaused() public {
|
||||||
|
CuT.pause();
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
CuT.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_cannotUnpauseWhenAlreadyUnpaused() public {
|
||||||
|
expectRevertWithContractIsNotPaused();
|
||||||
|
CuT.unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_shouldUnpauseWhenPausableIsFalse() public {
|
||||||
|
CuT.pause();
|
||||||
|
CuT.setPausable(false);
|
||||||
|
|
||||||
|
CuT.unpause();
|
||||||
|
assertFalse(CuT.isPaused());
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_cannotPauseWhenPausableIsFalse() public {
|
||||||
|
CuT.setPausable(false);
|
||||||
|
|
||||||
|
expectRevertWithContractIsNotPausable();
|
||||||
|
CuT.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_cannotSetPausableWhenIsAlreadyTrue() public {
|
||||||
|
expectRevertWithPausableIsSetTo(true);
|
||||||
|
CuT.setPausable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_cannotSetPausableWhenIsAlreadyFalse() public {
|
||||||
|
CuT.setPausable(false);
|
||||||
|
|
||||||
|
expectRevertWithPausableIsSetTo(false);
|
||||||
|
CuT.setPausable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_shouldRevertForFunctionsWhenContractIsPaused() public {
|
||||||
|
address randomAddress = address(1);
|
||||||
|
uint256 tokenId = mintDefault(deployer);
|
||||||
|
CuT.pause();
|
||||||
|
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
mintDefault(deployer);
|
||||||
|
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
CuT.burn(tokenId);
|
||||||
|
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
CuT.transferFrom(deployer, randomAddress, tokenId);
|
||||||
|
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
CuT.addAccessPoint(tokenId, "accesspoint.com");
|
||||||
|
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
CuT.removeAccessPoint("accesspoint.com");
|
||||||
|
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
CuT.grantCollectionRole(FleekAccessControl.Roles.Controller, randomAddress);
|
||||||
|
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
CuT.revokeCollectionRole(FleekAccessControl.Roles.Controller, randomAddress);
|
||||||
|
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
CuT.grantTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress);
|
||||||
|
|
||||||
|
expectRevertWithContractIsPaused();
|
||||||
|
CuT.revokeTokenRole(tokenId, FleekAccessControl.Roles.Controller, randomAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
export const Errors = Object.freeze({
|
export const Errors = Object.freeze({
|
||||||
|
ContractIsPaused: 'ContractIsPaused',
|
||||||
|
ContractIsNotPaused: 'ContractIsNotPaused',
|
||||||
|
ContractIsNotPausable: 'ContractIsNotPausable',
|
||||||
|
PausableIsSetTo: 'PausableIsSetTo',
|
||||||
ThereIsNoTokenMinted: 'ThereIsNoTokenMinted',
|
ThereIsNoTokenMinted: 'ThereIsNoTokenMinted',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { ethers, upgrades } from 'hardhat';
|
||||||
import { TestConstants } from './constants';
|
import { TestConstants } from './constants';
|
||||||
|
|
||||||
export abstract class Fixtures {
|
export abstract class Fixtures {
|
||||||
|
static async paused() {}
|
||||||
|
|
||||||
static async default() {
|
static async default() {
|
||||||
// Contracts are deployed using the first signer/account by default
|
// Contracts are deployed using the first signer/account by default
|
||||||
const [owner, otherAccount] = await ethers.getSigners();
|
const [owner, otherAccount] = await ethers.getSigners();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { TestConstants, Fixtures, Errors } from './helpers';
|
||||||
|
|
||||||
|
const { MintParams, Roles } = TestConstants;
|
||||||
|
|
||||||
|
describe('FleekERC721.Pausable', () => {
|
||||||
|
let fixture: Awaited<ReturnType<typeof Fixtures.default>>;
|
||||||
|
|
||||||
|
const mint = () => {
|
||||||
|
const { owner, contract } = fixture;
|
||||||
|
|
||||||
|
return contract.mint(
|
||||||
|
owner.address,
|
||||||
|
MintParams.name,
|
||||||
|
MintParams.description,
|
||||||
|
MintParams.externalUrl,
|
||||||
|
MintParams.ens,
|
||||||
|
MintParams.commitHash,
|
||||||
|
MintParams.gitRepository,
|
||||||
|
MintParams.logo,
|
||||||
|
MintParams.color
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
fixture = await loadFixture(Fixtures.default);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be initalized as paused and pausable', async () => {
|
||||||
|
const { contract } = fixture;
|
||||||
|
|
||||||
|
expect(await contract.isPaused()).to.be.false;
|
||||||
|
expect(await contract.isPausable()).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unpause', async () => {
|
||||||
|
const { contract } = fixture;
|
||||||
|
|
||||||
|
await contract.pause();
|
||||||
|
await contract.unpause();
|
||||||
|
|
||||||
|
expect(await contract.isPaused()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pause', async () => {
|
||||||
|
const { contract } = fixture;
|
||||||
|
|
||||||
|
await contract.pause();
|
||||||
|
|
||||||
|
expect(await contract.isPaused()).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow pause if is paused', async () => {
|
||||||
|
const { contract } = fixture;
|
||||||
|
|
||||||
|
await contract.pause();
|
||||||
|
|
||||||
|
await expect(contract.pause()).to.be.revertedWithCustomError(
|
||||||
|
contract,
|
||||||
|
Errors.ContractIsPaused
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow unpause if is not paused', async () => {
|
||||||
|
const { contract } = fixture;
|
||||||
|
|
||||||
|
await expect(contract.unpause()).to.be.revertedWithCustomError(
|
||||||
|
contract,
|
||||||
|
Errors.ContractIsNotPaused
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unpause when contract is not pausable', async () => {
|
||||||
|
const { contract } = fixture;
|
||||||
|
|
||||||
|
await contract.pause();
|
||||||
|
await contract.setPausable(false);
|
||||||
|
await contract.unpause();
|
||||||
|
|
||||||
|
expect(await contract.isPaused()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow pause when contract is not pausable', async () => {
|
||||||
|
const { contract } = fixture;
|
||||||
|
|
||||||
|
await contract.setPausable(false);
|
||||||
|
|
||||||
|
await expect(contract.pause()).to.be.revertedWithCustomError(
|
||||||
|
contract,
|
||||||
|
Errors.ContractIsNotPausable
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow set pausable if is already set', async () => {
|
||||||
|
const { contract } = fixture;
|
||||||
|
|
||||||
|
await expect(contract.setPausable(true))
|
||||||
|
.to.be.revertedWithCustomError(contract, Errors.PausableIsSetTo)
|
||||||
|
.withArgs(true);
|
||||||
|
|
||||||
|
await contract.setPausable(false);
|
||||||
|
|
||||||
|
await expect(contract.setPausable(false))
|
||||||
|
.to.be.revertedWithCustomError(contract, Errors.PausableIsSetTo)
|
||||||
|
.withArgs(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow call functions when paused', async () => {
|
||||||
|
const { contract, owner, otherAccount } = fixture;
|
||||||
|
const tokenId = (await mint()).value.toNumber();
|
||||||
|
await contract.pause();
|
||||||
|
|
||||||
|
await expect(mint()).to.be.revertedWithCustomError(
|
||||||
|
contract,
|
||||||
|
Errors.ContractIsPaused
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(contract.burn(tokenId)).to.be.revertedWithCustomError(
|
||||||
|
contract,
|
||||||
|
Errors.ContractIsPaused
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
contract.transferFrom(owner.address, otherAccount.address, tokenId)
|
||||||
|
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
contract.addAccessPoint(tokenId, 'accesspoint.com')
|
||||||
|
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
contract.removeAccessPoint('accesspoint.com')
|
||||||
|
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
contract.grantCollectionRole(Roles.Controller, otherAccount.address)
|
||||||
|
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
contract.revokeCollectionRole(Roles.Controller, otherAccount.address)
|
||||||
|
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
contract.grantTokenRole(Roles.Controller, tokenId, otherAccount.address)
|
||||||
|
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
contract.revokeTokenRole(Roles.Controller, tokenId, otherAccount.address)
|
||||||
|
).to.be.revertedWithCustomError(contract, Errors.ContractIsPaused);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue