Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENG-14] add ability to pause deposits #72

Merged
merged 4 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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