Skip to content

Commit

Permalink
[ENG-14] add ability to pause deposits (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
eyqs authored Oct 24, 2024
1 parent d21f7b8 commit eb5f70c
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 5 deletions.
53 changes: 53 additions & 0 deletions staking/src/RWAStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable, ReentrancyGuar
mapping(IERC20 stablecoin => bool allowed) allowedStablecoins;
/// @dev Timestamp of when pre-staking ends, when the admin withdraws all stablecoins
uint256 endTime;
/// @dev True if the RWAStaking contract is paused for deposits, false otherwise
bool paused;
}

// keccak256(abi.encode(uint256(keccak256("plume.storage.RWAStaking")) - 1)) & ~bytes32(uint256(0xff))
Expand Down Expand Up @@ -92,8 +94,23 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable, ReentrancyGuar
*/
event Staked(address indexed user, IERC20 indexed stablecoin, uint256 amount);

/// @notice Emitted when the RWAStaking contract is paused for deposits
event Paused();

/// @notice Emitted when the RWAStaking contract is unpaused for deposits
event Unpaused();

// Errors

/// @notice Indicates a failure because the contract is paused for deposits
error DepositPaused();

/// @notice Indicates a failure because the contract is already paused for deposits
error AlreadyPaused();

/// @notice Indicates a failure because the contract is not paused for deposits
error NotPaused();

/// @notice Indicates a failure because the pre-staking period has ended
error StakingEnded();

Expand Down Expand Up @@ -193,6 +210,34 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable, ReentrancyGuar
$.endTime = block.timestamp;
}

/**
* @notice Pause the RWAStaking contract for deposits
* @dev Only the admin can pause the RWAStaking contract for deposits
*/
function pause() external onlyRole(ADMIN_ROLE) {
RWAStakingStorage storage $ = _getRWAStakingStorage();
if ($.paused) {
revert AlreadyPaused();
}
$.paused = true;
emit Paused();
}

// Errors

/**
* @notice Unpause the RWAStaking contract for deposits
* @dev Only the admin can unpause the RWAStaking contract for deposits
*/
function unpause() external onlyRole(ADMIN_ROLE) {
RWAStakingStorage storage $ = _getRWAStakingStorage();
if (!$.paused) {
revert NotPaused();
}
$.paused = false;
emit Unpaused();
}

// User Functions

/**
Expand All @@ -205,6 +250,9 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable, ReentrancyGuar
if ($.endTime != 0) {
revert StakingEnded();
}
if ($.paused) {
revert DepositPaused();
}
if (!$.allowedStablecoins[stablecoin]) {
revert NotAllowedStablecoin(stablecoin);
}
Expand Down Expand Up @@ -303,4 +351,9 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable, ReentrancyGuar
return _getRWAStakingStorage().endTime;
}

/// @notice Returns true if the RWAStaking contract is pauseWhether the RWAStaking contract is paused for deposits
function isPaused() external view returns (bool) {
return _getRWAStakingStorage().paused;
}

}
63 changes: 58 additions & 5 deletions staking/src/ReserveStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable, Reentrancy
mapping(address user => UserState userState) userStates;
/// @dev Timestamp of when pre-staking ends, when the admin withdraws all SBTC and STONE
uint256 endTime;
/// @dev True if the ReserveStaking contract is paused for deposits, false otherwise
bool paused;
}

// keccak256(abi.encode(uint256(keccak256("plume.storage.ReserveStaking")) - 1)) & ~bytes32(uint256(0xff))
Expand Down Expand Up @@ -102,16 +104,31 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable, Reentrancy
*/
event Staked(address indexed user, uint256 sbtcAmount, uint256 stoneAmount);

/// @notice Emitted when the ReserveStaking contract is paused for deposits
event Paused();

/// @notice Emitted when the ReserveStaking contract is unpaused for deposits
event Unpaused();

// Errors

/// @notice Indicates a failure because the contract is paused for deposits
error DepositPaused();

/// @notice Indicates a failure because the contract is already paused for deposits
error AlreadyPaused();

/// @notice Indicates a failure because the contract is not paused for deposits
error NotPaused();

/// @notice Indicates a failure because the pre-staking period has ended
error StakingEnded();

/**
* @notice Indicates a failure because the user does not have enough SBTC or STONE staked
* @param user Address of the user who does not have enough SBTC or STONE staked
* @param sbtcAmount Amount of SBTC that the user does not have enough of
* @param stoneAmount Amount of STONE that the user does not have enough of
* @param sbtcAmount Amount of SBTC that the user wants to withdraw
* @param stoneAmount Amount of STONE that the user wants to withdraw
* @param sbtcAmountStaked Amount of SBTC that the user has staked
* @param stoneAmountStaked Amount of STONE that the user has staked
*/
Expand Down Expand Up @@ -144,8 +161,9 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable, Reentrancy
_grantRole(ADMIN_ROLE, owner);
_grantRole(UPGRADER_ROLE, owner);

_getReserveStakingStorage().sbtc = sbtc;
_getReserveStakingStorage().stone = stone;
ReserveStakingStorage storage $ = _getReserveStakingStorage();
$.sbtc = sbtc;
$.stone = stone;
}

// Override Functions
Expand Down Expand Up @@ -180,6 +198,32 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable, Reentrancy
emit AdminWithdrawn(msg.sender, sbtcAmount, stoneAmount);
}

/**
* @notice Pause the ReserveStaking contract for deposits
* @dev Only the admin can pause the ReserveStaking contract for deposits
*/
function pause() external onlyRole(ADMIN_ROLE) {
ReserveStakingStorage storage $ = _getReserveStakingStorage();
if ($.paused) {
revert AlreadyPaused();
}
$.paused = true;
emit Paused();
}

/**
* @notice Unpause the ReserveStaking contract for deposits
* @dev Only the admin can unpause the ReserveStaking contract for deposits
*/
function unpause() external onlyRole(ADMIN_ROLE) {
ReserveStakingStorage storage $ = _getReserveStakingStorage();
if (!$.paused) {
revert NotPaused();
}
$.paused = false;
emit Unpaused();
}

// User Functions

/**
Expand All @@ -192,6 +236,9 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable, Reentrancy
if ($.endTime != 0) {
revert StakingEnded();
}
if ($.paused) {
revert DepositPaused();
}

uint256 timestamp = block.timestamp;
UserState storage userState = $.userStates[msg.sender];
Expand Down Expand Up @@ -271,7 +318,8 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable, Reentrancy
stone.safeTransfer(msg.sender, stoneAmount);
uint256 newBalance = stone.balanceOf(address(this));
actualStoneAmount = previousBalance - newBalance;
userState.stoneAmountSeconds -= userState.stoneAmountSeconds * actualStoneAmount / userState.stoneAmountStaked;
userState.stoneAmountSeconds -=
userState.stoneAmountSeconds * actualStoneAmount / userState.stoneAmountStaked;
userState.stoneAmountStaked -= actualStoneAmount;
userState.stoneLastUpdate = timestamp;
$.stoneTotalAmountStaked -= actualStoneAmount;
Expand Down Expand Up @@ -330,4 +378,9 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable, Reentrancy
return _getReserveStakingStorage().endTime;
}

/// @notice Returns true if the ReserveStaking contract is paused for deposits, otherwise false
function isPaused() external view returns (bool) {
return _getReserveStakingStorage().paused;
}

}
76 changes: 76 additions & 0 deletions staking/test/RWAStaking.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ contract RWAStakingTest is Test {
rwaStaking.stake(100 ether, usdc);
vm.expectRevert(abi.encodeWithSelector(RWAStaking.StakingEnded.selector));
rwaStaking.adminWithdraw();
vm.expectRevert(abi.encodeWithSelector(RWAStaking.StakingEnded.selector));
rwaStaking.withdraw(100 ether, usdc);

vm.stopPrank();
}
Expand Down Expand Up @@ -216,6 +218,80 @@ contract RWAStakingTest is Test {
assertEq(rwaStaking.getEndTime(), startTime + timeskipAmount);
}

function test_pauseFail() public {
vm.startPrank(user1);

vm.expectRevert(
abi.encodeWithSelector(
IAccessControl.AccessControlUnauthorizedAccount.selector, user1, rwaStaking.ADMIN_ROLE()
)
);
rwaStaking.pause();

vm.stopPrank();

vm.startPrank(owner);

vm.expectEmit(false, false, false, true, address(rwaStaking));
emit RWAStaking.Paused();
rwaStaking.pause();

vm.expectRevert(abi.encodeWithSelector(RWAStaking.AlreadyPaused.selector));
rwaStaking.pause();

vm.stopPrank();
}

function test_pause() public {
vm.startPrank(owner);

assertEq(rwaStaking.isPaused(), false);

vm.expectEmit(false, false, false, true, address(rwaStaking));
emit RWAStaking.Paused();
rwaStaking.pause();
assertEq(rwaStaking.isPaused(), true);

vm.expectRevert(abi.encodeWithSelector(RWAStaking.DepositPaused.selector));
rwaStaking.stake(100 ether, usdc);

vm.stopPrank();
}

function test_unpauseFail() public {
vm.startPrank(user1);

vm.expectRevert(
abi.encodeWithSelector(
IAccessControl.AccessControlUnauthorizedAccount.selector, user1, rwaStaking.ADMIN_ROLE()
)
);
rwaStaking.unpause();

vm.stopPrank();

vm.startPrank(owner);

vm.expectRevert(abi.encodeWithSelector(RWAStaking.NotPaused.selector));
rwaStaking.unpause();

vm.stopPrank();
}

function test_unpause() public {
vm.startPrank(owner);

rwaStaking.pause();
assertEq(rwaStaking.isPaused(), true);

vm.expectEmit(false, false, false, true, address(rwaStaking));
emit RWAStaking.Unpaused();
rwaStaking.unpause();
assertEq(rwaStaking.isPaused(), false);

vm.stopPrank();
}

function test_withdrawFail() public {
uint256 stakeAmount = 100 ether;

Expand Down
74 changes: 74 additions & 0 deletions staking/test/ReserveStaking.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,80 @@ contract ReserveStakingTest is Test {
assertEq(staking.getEndTime(), startTime + timeskipAmount);
}

function test_pauseFail() public {
vm.startPrank(user1);

vm.expectRevert(
abi.encodeWithSelector(
IAccessControl.AccessControlUnauthorizedAccount.selector, user1, staking.ADMIN_ROLE()
)
);
staking.pause();

vm.stopPrank();

vm.startPrank(owner);

vm.expectEmit(false, false, false, true, address(staking));
emit ReserveStaking.Paused();
staking.pause();

vm.expectRevert(abi.encodeWithSelector(ReserveStaking.AlreadyPaused.selector));
staking.pause();

vm.stopPrank();
}

function test_pause() public {
vm.startPrank(owner);

assertEq(staking.isPaused(), false);

vm.expectEmit(false, false, false, true, address(staking));
emit ReserveStaking.Paused();
staking.pause();
assertEq(staking.isPaused(), true);

vm.expectRevert(abi.encodeWithSelector(ReserveStaking.DepositPaused.selector));
staking.stake(100 ether, 0);

vm.stopPrank();
}

function test_unpauseFail() public {
vm.startPrank(user1);

vm.expectRevert(
abi.encodeWithSelector(
IAccessControl.AccessControlUnauthorizedAccount.selector, user1, staking.ADMIN_ROLE()
)
);
staking.unpause();

vm.stopPrank();

vm.startPrank(owner);

vm.expectRevert(abi.encodeWithSelector(ReserveStaking.NotPaused.selector));
staking.unpause();

vm.stopPrank();
}

function test_unpause() public {
vm.startPrank(owner);

staking.pause();
assertEq(staking.isPaused(), true);

vm.expectEmit(false, false, false, true, address(staking));
emit ReserveStaking.Unpaused();
staking.unpause();
assertEq(staking.isPaused(), false);

vm.stopPrank();
}

function test_withdrawFail() public {
uint256 sbtcAmount = 100 ether;
uint256 stoneAmount = 50 ether;
Expand Down

0 comments on commit eb5f70c

Please sign in to comment.