From f197f402eecd6d2e644e37c19f769f5a9783c2be Mon Sep 17 00:00:00 2001 From: "Eugene Y. Q. Shen" Date: Wed, 23 Oct 2024 15:05:41 -0400 Subject: [PATCH 1/3] [FEA-1063] add user withdrawal method to RWA staking --- staking/src/RWAStaking.sol | 57 +++++++++++++++++++-- staking/test/RWAStaking.t.sol | 94 ++++++++++++++++++++++++++++++----- 2 files changed, 134 insertions(+), 17 deletions(-) diff --git a/staking/src/RWAStaking.sol b/staking/src/RWAStaking.sol index 5948996..11a552a 100644 --- a/staking/src/RWAStaking.sol +++ b/staking/src/RWAStaking.sol @@ -73,6 +73,14 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { * @param stablecoin Stablecoin token contract address * @param amount Amount of stablecoins withdrawn */ + event AdminWithdrawn(address indexed user, IERC20 indexed stablecoin, uint256 amount); + + /** + * @notice Emitted when a user withdraws stablecoins from the RWAStaking contract + * @param user Address of the user who withdrew stablecoins + * @param stablecoin Stablecoin token contract address + * @param amount Amount of stablecoins withdrawn + */ event Withdrawn(address indexed user, IERC20 indexed stablecoin, uint256 amount); /** @@ -80,9 +88,8 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { * @param user Address of the user who staked stablecoins * @param stablecoin Stablecoin token contract address * @param amount Amount of stablecoins staked - * @param timestamp Timestamp of the stake */ - event Staked(address indexed user, IERC20 indexed stablecoin, uint256 amount, uint256 timestamp); + event Staked(address indexed user, IERC20 indexed stablecoin, uint256 amount); // Errors @@ -101,6 +108,15 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { */ error NotAllowedStablecoin(IERC20 stablecoin); + /** + * @notice Indicates a failure because the user does not have enough stablecoins staked + * @param user Address of the user who does not have enough stablecoins staked + * @param stablecoin Stablecoin token contract address + * @param amount Amount of stablecoins that the user wants to withdraw + * @param amountStaked Amount of stablecoins that the user has staked + */ + error InsufficientStaked(address user, IERC20 stablecoin, uint256 amount, uint256 amountStaked); + // Initializer /** @@ -158,7 +174,7 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { * @notice Stop the RWAStaking contract by withdrawing all stablecoins * @dev Only the admin can withdraw stablecoins from the RWAStaking contract */ - function withdraw() external onlyRole(ADMIN_ROLE) { + function adminWithdraw() external onlyRole(ADMIN_ROLE) { RWAStakingStorage storage $ = _getRWAStakingStorage(); if ($.endTime != 0) { revert StakingEnded(); @@ -170,7 +186,7 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { IERC20 stablecoin = stablecoins[i]; uint256 amount = stablecoin.balanceOf(address(this)); stablecoin.safeTransfer(msg.sender, amount); - emit Withdrawn(msg.sender, stablecoin, amount); + emit AdminWithdrawn(msg.sender, stablecoin, amount); } $.endTime = block.timestamp; } @@ -208,7 +224,38 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { userState.lastUpdate = timestamp; $.totalAmountStaked += actualAmount; - emit Staked(msg.sender, stablecoin, actualAmount, timestamp); + emit Staked(msg.sender, stablecoin, actualAmount); + } + + /** + * @notice Withdraw stablecoins from the RWAStaking contract + * @param amount Amount of stablecoins to withdraw + * @param stablecoin Stablecoin token contract address + */ + function withdraw(uint256 amount, IERC20 stablecoin) external { + RWAStakingStorage storage $ = _getRWAStakingStorage(); + if ($.endTime != 0) { + revert StakingEnded(); + } + + uint256 timestamp = block.timestamp; + UserState storage userState = $.userStates[msg.sender]; + if (userState.amountStaked < amount) { + revert InsufficientStaked(msg.sender, stablecoin, amount, userState.amountStaked); + } + + userState.amountSeconds += userState.amountStaked * (timestamp - userState.lastUpdate); + uint256 previousBalance = stablecoin.balanceOf(address(this)); + stablecoin.safeTransfer(msg.sender, amount); + uint256 newBalance = stablecoin.balanceOf(address(this)); + uint256 actualAmount = previousBalance - newBalance; + + userState.amountSeconds -= userState.amountSeconds * amount / userState.amountStaked; + userState.amountStaked -= actualAmount; + userState.lastUpdate = timestamp; + $.totalAmountStaked -= actualAmount; + + emit Withdrawn(msg.sender, stablecoin, actualAmount); } // Getter View Functions diff --git a/staking/test/RWAStaking.t.sol b/staking/test/RWAStaking.t.sol index ff307e5..b926082 100644 --- a/staking/test/RWAStaking.t.sol +++ b/staking/test/RWAStaking.t.sol @@ -65,7 +65,7 @@ contract RWAStakingTest is Test { vm.startPrank(user); usdc.approve(address(rwaStaking), stakeAmount); vm.expectEmit(true, true, false, true, address(rwaStaking)); - emit RWAStaking.Staked(user, usdc, stakeAmount, block.timestamp); + emit RWAStaking.Staked(user, usdc, stakeAmount); rwaStaking.stake(stakeAmount, usdc); vm.stopPrank(); @@ -109,12 +109,12 @@ contract RWAStakingTest is Test { function test_stakingEnded() public { vm.startPrank(owner); - rwaStaking.withdraw(); + rwaStaking.adminWithdraw(); vm.expectRevert(abi.encodeWithSelector(RWAStaking.StakingEnded.selector)); rwaStaking.stake(100 ether, usdc); vm.expectRevert(abi.encodeWithSelector(RWAStaking.StakingEnded.selector)); - rwaStaking.withdraw(); + rwaStaking.adminWithdraw(); vm.stopPrank(); } @@ -152,18 +152,18 @@ contract RWAStakingTest is Test { vm.stopPrank(); } - function test_withdrawFail() public { + function test_adminWithdrawFail() public { vm.expectRevert( abi.encodeWithSelector( IAccessControl.AccessControlUnauthorizedAccount.selector, user1, rwaStaking.ADMIN_ROLE() ) ); vm.startPrank(user1); - rwaStaking.withdraw(); + rwaStaking.adminWithdraw(); vm.stopPrank(); } - function test_withdraw() public { + function test_adminWithdraw() public { uint256 stakeAmount = 100 ether; uint256 pusdStakeAmount = 30 ether; uint256 timeskipAmount = 300; @@ -196,10 +196,10 @@ contract RWAStakingTest is Test { vm.startPrank(owner); vm.expectEmit(true, true, false, true, address(rwaStaking)); - emit RWAStaking.Withdrawn(owner, usdc, stakeAmount); + emit RWAStaking.AdminWithdrawn(owner, usdc, stakeAmount); vm.expectEmit(true, true, false, true, address(rwaStaking)); - emit RWAStaking.Withdrawn(owner, pusd, pusdStakeAmount); - rwaStaking.withdraw(); + emit RWAStaking.AdminWithdrawn(owner, pusd, pusdStakeAmount); + rwaStaking.adminWithdraw(); vm.stopPrank(); // Skip ahead in time by 300 seconds and check that amountSeconds is fixed @@ -216,6 +216,76 @@ contract RWAStakingTest is Test { assertEq(rwaStaking.getEndTime(), startTime + timeskipAmount); } + function test_withdrawFail() public { + uint256 stakeAmount = 100 ether; + + // Stake from user1 so we can test withdrawals + helper_initialStake(user1, stakeAmount); + + vm.startPrank(user1); + + uint256 withdrawAmount = stakeAmount + 1; + vm.expectRevert( + abi.encodeWithSelector(RWAStaking.InsufficientStaked.selector, user1, usdc, withdrawAmount, stakeAmount) + ); + rwaStaking.withdraw(withdrawAmount, usdc); + + vm.stopPrank(); + } + + function test_withdraw() public { + uint256 stakeAmount = 100 ether; + uint256 timeskipAmount = 300; + uint256 startTime = block.timestamp; + + // Stake from user1 + helper_initialStake(user1, stakeAmount); + + (uint256 amountSeconds, uint256 amountStaked, uint256 lastUpdate) = rwaStaking.getUserState(user1); + assertEq(amountSeconds, 0); + assertEq(amountStaked, stakeAmount); + assertEq(lastUpdate, startTime); + assertEq(usdc.balanceOf(address(rwaStaking)), stakeAmount); + assertEq(usdc.balanceOf(user1), INITIAL_BALANCE - stakeAmount); + + // Skip ahead in time by 300 seconds + vm.warp(startTime + timeskipAmount); + + // Withdraw half of the staked amount + uint256 withdrawAmount = stakeAmount / 2; + vm.startPrank(user1); + vm.expectEmit(true, true, false, true, address(rwaStaking)); + emit RWAStaking.Withdrawn(user1, usdc, withdrawAmount); + rwaStaking.withdraw(withdrawAmount, usdc); + vm.stopPrank(); + + // Check updated balances and state + (amountSeconds, amountStaked, lastUpdate) = rwaStaking.getUserState(user1); + assertEq(amountSeconds, stakeAmount * timeskipAmount / 2); + assertEq(amountStaked, stakeAmount / 2); + assertEq(lastUpdate, startTime + timeskipAmount); + assertEq(usdc.balanceOf(address(rwaStaking)), stakeAmount / 2); + assertEq(usdc.balanceOf(user1), INITIAL_BALANCE - stakeAmount / 2); + + // Skip ahead in time by another 300 seconds + vm.warp(startTime + timeskipAmount * 2); + + // Withdraw remaining amounts + vm.startPrank(user1); + vm.expectEmit(true, true, false, true, address(rwaStaking)); + emit RWAStaking.Withdrawn(user1, usdc, stakeAmount / 2); + rwaStaking.withdraw(stakeAmount / 2, usdc); + vm.stopPrank(); + + // Check final balances and state + (amountSeconds, amountStaked, lastUpdate) = rwaStaking.getUserState(user1); + assertEq(amountSeconds, 0); + assertEq(amountStaked, 0); + assertEq(lastUpdate, startTime + timeskipAmount * 2); + assertEq(usdc.balanceOf(address(rwaStaking)), 0); + assertEq(usdc.balanceOf(user1), INITIAL_BALANCE); + } + function test_stakeFail() public { uint256 stakeAmount = 100 ether; @@ -273,10 +343,10 @@ contract RWAStakingTest is Test { usdc.approve(address(rwaStaking), stakeAmount); pusd.approve(address(rwaStaking), stakeAmount); vm.expectEmit(true, true, false, true, address(rwaStaking)); - emit RWAStaking.Staked(user2, usdc, stakeAmount, startTime + timeskipAmount); + emit RWAStaking.Staked(user2, usdc, stakeAmount); rwaStaking.stake(stakeAmount, usdc); vm.expectEmit(true, true, false, true, address(rwaStaking)); - emit RWAStaking.Staked(user2, pusd, stakeAmount, startTime + timeskipAmount); + emit RWAStaking.Staked(user2, pusd, stakeAmount); rwaStaking.stake(stakeAmount, pusd); vm.stopPrank(); @@ -299,7 +369,7 @@ contract RWAStakingTest is Test { vm.startPrank(user1); pusd.approve(address(rwaStaking), stakeAmount); vm.expectEmit(true, true, false, true, address(rwaStaking)); - emit RWAStaking.Staked(user1, pusd, stakeAmount, startTime + timeskipAmount * 2); + emit RWAStaking.Staked(user1, pusd, stakeAmount); rwaStaking.stake(stakeAmount, pusd); vm.stopPrank(); vm.warp(startTime + timeskipAmount * 3); From 3266a02100dc01042865515f6c9b4278991824b0 Mon Sep 17 00:00:00 2001 From: "Eugene Y. Q. Shen" Date: Wed, 23 Oct 2024 16:20:18 -0700 Subject: [PATCH 2/3] [FEA-1063] remediate Slowmist audit suggestion [N3] (#71) --- staking/src/RWAStaking.sol | 2 +- staking/src/ReserveStaking.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/staking/src/RWAStaking.sol b/staking/src/RWAStaking.sol index 11a552a..413019b 100644 --- a/staking/src/RWAStaking.sol +++ b/staking/src/RWAStaking.sol @@ -250,7 +250,7 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { uint256 newBalance = stablecoin.balanceOf(address(this)); uint256 actualAmount = previousBalance - newBalance; - userState.amountSeconds -= userState.amountSeconds * amount / userState.amountStaked; + userState.amountSeconds -= userState.amountSeconds * actualAmount / userState.amountStaked; userState.amountStaked -= actualAmount; userState.lastUpdate = timestamp; $.totalAmountStaked -= actualAmount; diff --git a/staking/src/ReserveStaking.sol b/staking/src/ReserveStaking.sol index d26e5d9..7cd51e1 100644 --- a/staking/src/ReserveStaking.sol +++ b/staking/src/ReserveStaking.sol @@ -255,7 +255,7 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable { sbtc.safeTransfer(msg.sender, sbtcAmount); uint256 newBalance = sbtc.balanceOf(address(this)); actualSbtcAmount = previousBalance - newBalance; - userState.sbtcAmountSeconds -= userState.sbtcAmountSeconds * sbtcAmount / userState.sbtcAmountStaked; + userState.sbtcAmountSeconds -= userState.sbtcAmountSeconds * actualSbtcAmount / userState.sbtcAmountStaked; userState.sbtcAmountStaked -= actualSbtcAmount; userState.sbtcLastUpdate = timestamp; $.sbtcTotalAmountStaked -= actualSbtcAmount; @@ -268,7 +268,7 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable { stone.safeTransfer(msg.sender, stoneAmount); uint256 newBalance = stone.balanceOf(address(this)); actualStoneAmount = previousBalance - newBalance; - userState.stoneAmountSeconds -= userState.stoneAmountSeconds * stoneAmount / userState.stoneAmountStaked; + userState.stoneAmountSeconds -= userState.stoneAmountSeconds * actualStoneAmount / userState.stoneAmountStaked; userState.stoneAmountStaked -= actualStoneAmount; userState.stoneLastUpdate = timestamp; $.stoneTotalAmountStaked -= actualStoneAmount; From f1ea750246c015cc7948f6563d3306b69bae84ff Mon Sep 17 00:00:00 2001 From: "Eugene Y. Q. Shen" Date: Wed, 23 Oct 2024 20:37:34 -0400 Subject: [PATCH 3/3] [ENG-14] add ability to pause deposits --- staking/src/RWAStaking.sol | 53 +++++++++++++++++++++ staking/src/ReserveStaking.sol | 63 +++++++++++++++++++++++-- staking/test/RWAStaking.t.sol | 76 +++++++++++++++++++++++++++++++ staking/test/ReserveStaking.t.sol | 74 ++++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 5 deletions(-) diff --git a/staking/src/RWAStaking.sol b/staking/src/RWAStaking.sol index 413019b..0533618 100644 --- a/staking/src/RWAStaking.sol +++ b/staking/src/RWAStaking.sol @@ -46,6 +46,8 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { 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)) @@ -91,8 +93,23 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { */ 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(); @@ -191,6 +208,34 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { $.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 /** @@ -203,6 +248,9 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { if ($.endTime != 0) { revert StakingEnded(); } + if ($.paused) { + revert DepositPaused(); + } if (!$.allowedStablecoins[stablecoin]) { revert NotAllowedStablecoin(stablecoin); } @@ -301,4 +349,9 @@ contract RWAStaking is AccessControlUpgradeable, UUPSUpgradeable { 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; + } + } diff --git a/staking/src/ReserveStaking.sol b/staking/src/ReserveStaking.sol index 7cd51e1..a1060de 100644 --- a/staking/src/ReserveStaking.sol +++ b/staking/src/ReserveStaking.sol @@ -55,6 +55,8 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable { 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)) @@ -100,16 +102,31 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable { */ 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 */ @@ -141,8 +158,9 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable { _grantRole(ADMIN_ROLE, owner); _grantRole(UPGRADER_ROLE, owner); - _getReserveStakingStorage().sbtc = sbtc; - _getReserveStakingStorage().stone = stone; + ReserveStakingStorage storage $ = _getReserveStakingStorage(); + $.sbtc = sbtc; + $.stone = stone; } // Override Functions @@ -177,6 +195,32 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable { 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 /** @@ -189,6 +233,9 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable { if ($.endTime != 0) { revert StakingEnded(); } + if ($.paused) { + revert DepositPaused(); + } uint256 timestamp = block.timestamp; UserState storage userState = $.userStates[msg.sender]; @@ -268,7 +315,8 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable { 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; @@ -327,4 +375,9 @@ contract ReserveStaking is AccessControlUpgradeable, UUPSUpgradeable { 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; + } + } diff --git a/staking/test/RWAStaking.t.sol b/staking/test/RWAStaking.t.sol index b926082..15c322a 100644 --- a/staking/test/RWAStaking.t.sol +++ b/staking/test/RWAStaking.t.sol @@ -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(); } @@ -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; diff --git a/staking/test/ReserveStaking.t.sol b/staking/test/ReserveStaking.t.sol index 89898d7..ca3f9de 100644 --- a/staking/test/ReserveStaking.t.sol +++ b/staking/test/ReserveStaking.t.sol @@ -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;