Skip to content

Commit

Permalink
feat: granular and global pausable contract
Browse files Browse the repository at this point in the history
  • Loading branch information
gomesalexandre committed Apr 9, 2024
1 parent 63f969b commit 1994426
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 5 deletions.
61 changes: 57 additions & 4 deletions foundry/src/FoxStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ pragma solidity ^0.8.25;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {IFoxStaking, StakingInfo} from "./IFoxStaking.sol";
import {console} from "forge-std/Script.sol";

contract FoxStaking is
IFoxStaking,
Ownable(msg.sender) // Deployer is the owner
Ownable(msg.sender), // Deployer is the owner
Pausable
{
IERC20 public foxToken;
mapping(address => StakingInfo) public stakingInfo;

bool public stakingPaused = false;
bool public withdrawalsPaused = false;
bool public unstakingPaused = false;

uint256 public cooldownPeriod = 28 days;

event UpdateCooldownPeriod(uint256 newCooldownPeriod);
Expand All @@ -26,12 +32,59 @@ contract FoxStaking is
foxToken = IERC20(foxTokenAddress);
}

function pauseStaking() external onlyOwner {
stakingPaused = true;
}

function unpauseStaking() external onlyOwner {
stakingPaused = false;
}

function pauseWithdrawals() external onlyOwner {
withdrawalsPaused = true;
}

function unpauseWithdrawals() external onlyOwner {
withdrawalsPaused = false;
}

function pauseUnstaking() external onlyOwner {
unstakingPaused = true;
}

function unpauseUnstaking() external onlyOwner {
unstakingPaused = false;
}

function pause() public onlyOwner {
_pause();
}

function unpause() public onlyOwner {
_unpause();
}

modifier whenStakingUnpaused() {
require(!stakingPaused, "Staking is paused");
_;
}

modifier whenUnstakingUnpaused() {
require(!unstakingPaused, "Unstaking is paused");
_;
}

modifier whenWithdrawalsUnpaused() {
require(!withdrawalsPaused, "Withdrawals are paused");
_;
}

function setCooldownPeriod(uint256 _cooldownPeriod) external onlyOwner {
cooldownPeriod = _cooldownPeriod;
emit UpdateCooldownPeriod(_cooldownPeriod);
}

function stake(uint256 amount, string memory runeAddress) external {
function stake(uint256 amount, string memory runeAddress) external whenNotPaused whenStakingUnpaused {
require(bytes(runeAddress).length > 0, "Rune address cannot be empty");
require(amount > 0, "FOX amount to stake must be greater than 0");
// Transfer fundus from msg.sender to contract assuming allowance has been set - here goes nothing
Expand All @@ -46,7 +99,7 @@ contract FoxStaking is
emit Stake(msg.sender, amount, runeAddress);
}

function unstake(uint256 amount) external {
function unstake(uint256 amount) external whenNotPaused whenUnstakingUnpaused {
require(amount > 0, "Cannot unstake 0");
StakingInfo storage info = stakingInfo[msg.sender];

Expand All @@ -66,7 +119,7 @@ contract FoxStaking is
emit Unstake(msg.sender, amount);
}

function withdraw() external {
function withdraw() external whenNotPaused whenWithdrawalsUnpaused {
StakingInfo storage info = stakingInfo[msg.sender];

require(info.unstakingBalance > 0, "Cannot withdraw 0");
Expand Down
25 changes: 24 additions & 1 deletion foundry/src/IFoxStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,32 @@ struct StakingInfo {
}


/// @title WIP high-level interface for FOX token staking contract
/// @notice This interface outlines the functions for staking FOX tokens, managing RUNE addresses for rewards, and claiming 'em.
interface IFoxStaking {
/// @notice Pauses deposits
function pauseStaking() external;

/// @notice Unpauses deposits
function unpauseStaking() external;

/// @notice Pauses withdrawals
function pauseWithdrawals() external;

/// @notice Unpauses withdrawals
function unpauseWithdrawals() external;

/// @notice Pauses unstaking
function pauseUnstaking() external;

/// @notice Unpauses unstaking
function unpauseUnstaking() external;

// @notice Sets contract-level paused state
function pause() external;

/// @notice Sets contract-level unpaused state
function unpause() external;

/// @notice Allows a user to stake a specified amount of FOX tokens and assign a RUNE address for rewards - which can be changed later on.
/// This has to be initiated by the user itself i.e msg.sender only, cannot be called by an address for another
/// @param amount The amount of FOX tokens to be staked.
Expand Down
60 changes: 60 additions & 0 deletions foundry/test/FoxStaking.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "../src/FoxStaking.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";

contract MockFOXToken is ERC20 {
constructor() ERC20("Mock FOX Token", "FOX") {
Expand Down Expand Up @@ -58,6 +59,26 @@ contract FOXStakingTestStaking is Test {
foxStaking = new FoxStaking(address(foxToken));
}

function testCannotStakeWhenStakingPaused() public {
foxStaking.pauseStaking();

address user = address(0xBABE);
vm.startPrank(user);
vm.expectRevert("Staking is paused");
foxStaking.stake(1e18, "runeAddress");
vm.stopPrank();
}

function testCannotStakeWhenContractPaused() public {
foxStaking.pause();

address user = address(0xBABE);
vm.startPrank(user);
vm.expectRevert(abi.encodeWithSelector(Pausable.EnforcedPause.selector));
foxStaking.stake(1e18, "runeAddress");
vm.stopPrank();
}

function testStaking() public {
address[] memory users = new address[](3);
users[0] = address(0xBABE);
Expand Down Expand Up @@ -114,6 +135,27 @@ contract FOXStakingTestUnstake is Test {
vm.stopPrank();
}

function testCannotUnstakeWhenUnstakingPaused() public {
foxStaking.pauseUnstaking();

vm.startPrank(user);
vm.expectRevert("Unstaking is paused");
foxStaking.unstake(amount);
vm.stopPrank();
}

function testCannotUnstakeWhenContractPaused() public {
foxStaking.pause();

vm.startPrank(user);
vm.expectRevert(abi.encodeWithSelector(
Pausable.EnforcedPause.selector
)
);
foxStaking.unstake(amount);
vm.stopPrank();
}

function testunstake_cannotRequestZero() public {
vm.startPrank(user);

Expand Down Expand Up @@ -281,6 +323,24 @@ contract FOXStakingTestWithdraw is Test {
vm.stopPrank();
}

function testCannotWithdrawWhenWithdrawalsPaused() public {
foxStaking.pauseWithdrawals();

vm.startPrank(user);
vm.expectRevert("Withdrawals are paused"); // Make sure this matches the actual revert message used in your contract
foxStaking.withdraw();
vm.stopPrank();
}

function testCannotWithdrawWhenContractPaused() public {
foxStaking.pause();

vm.startPrank(user);
vm.expectRevert(abi.encodeWithSelector(Pausable.EnforcedPause.selector));
foxStaking.withdraw();
vm.stopPrank();
}

function testWithdraw_cannotWithdrawBeforeCooldown() public {
vm.startPrank(user);

Expand Down

0 comments on commit 1994426

Please sign in to comment.