From d5f2941602d7c336d68a1a38b433ec8ac451b1cc Mon Sep 17 00:00:00 2001 From: sandybradley Date: Mon, 29 Jul 2024 22:40:27 +0200 Subject: [PATCH] feat: 14 day cooldown withdraw, deposit min --- src/FoldCaptiveStaking.sol | 24 +++++++++------ test/BaseCaptiveTest.sol | 3 +- test/UnitTests.t.sol | 60 ++++++++++++++++++++++++++++---------- 3 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/FoldCaptiveStaking.sol b/src/FoldCaptiveStaking.sol index 6af2cfb..855e702 100644 --- a/src/FoldCaptiveStaking.sol +++ b/src/FoldCaptiveStaking.sol @@ -40,8 +40,9 @@ contract FoldCaptiveStaking is Owned(msg.sender) { error NotInitialized(); error ZeroLiquidity(); error WithdrawFailed(); - error WithdrawProRata(); error DepositCapReached(); + error DepositAmountBelowMinimum(); + error WithdrawalCooldownPeriodNotMet(); /// @param _positionManager The Canonical UniswapV3 PositionManager /// @param _pool The FOLD Pool to Reward @@ -138,6 +139,13 @@ contract FoldCaptiveStaking is Owned(msg.sender) { /// @dev The cap on deposits in the pool in liquidity, set to 0 if no cap uint256 public depositCap; + /// @dev Min deposit amount for Fold / Eth + uint256 public constant MINIMUM_DEPOSIT = 1 ether; + /// @dev Min lockup period + uint256 public constant COOLDOWN_PERIOD = 14 days; + + mapping(address => uint256) public depositTimeStamp; + /*////////////////////////////////////////////////////////////// CHEF //////////////////////////////////////////////////////////////*/ @@ -175,6 +183,8 @@ contract FoldCaptiveStaking is Owned(msg.sender) { /// @param amount1 The amount of token1 to deposit /// @param slippage Slippage on deposit out of 1e18 function deposit(uint256 amount0, uint256 amount1, uint256 slippage) external isInitialized { + if (amount0 < MINIMUM_DEPOSIT && amount1 < MINIMUM_DEPOSIT) revert DepositAmountBelowMinimum(); + collectFees(); collectRewards(); @@ -207,6 +217,8 @@ contract FoldCaptiveStaking is Owned(msg.sender) { revert DepositCapReached(); } + depositTimeStamp[msg.sender] = block.timestamp; + emit Deposit(msg.sender, amount0, amount1); } @@ -276,13 +288,11 @@ contract FoldCaptiveStaking is Owned(msg.sender) { /// @notice Withdraws liquidity from the pool /// @param liquidity The amount of liquidity to withdraw function withdraw(uint128 liquidity) external isInitialized { + if (block.timestamp < depositTimeStamp[msg.sender] + COOLDOWN_PERIOD) revert WithdrawalCooldownPeriodNotMet(); + collectFees(); collectRewards(); - if (liquidity > balances[msg.sender].amount / 2) { - revert WithdrawProRata(); - } - balances[msg.sender].amount -= liquidity; liquidityUnderManagement -= uint256(liquidity); @@ -349,10 +359,6 @@ contract FoldCaptiveStaking is Owned(msg.sender) { collectPositionFees(); collectRewards(); - if (liquidity > liquidityUnderManagement / 2) { - revert WithdrawProRata(); - } - liquidityUnderManagement -= uint256(liquidity); INonfungiblePositionManager.DecreaseLiquidityParams memory decreaseParams = INonfungiblePositionManager diff --git a/test/BaseCaptiveTest.sol b/test/BaseCaptiveTest.sol index 8a6a70b..27ea6ee 100644 --- a/test/BaseCaptiveTest.sol +++ b/test/BaseCaptiveTest.sol @@ -11,7 +11,8 @@ contract BaseCaptiveTest is Test { error NotInitialized(); error ZeroLiquidity(); error WithdrawFailed(); - error WithdrawProRata(); + error DepositAmountBelowMinimum(); + error WithdrawalCooldownPeriodNotMet(); FoldCaptiveStaking public foldCaptiveStaking; diff --git a/test/UnitTests.t.sol b/test/UnitTests.t.sol index 0d0b8a8..5a5787e 100644 --- a/test/UnitTests.t.sol +++ b/test/UnitTests.t.sol @@ -29,6 +29,9 @@ contract UnitTests is BaseCaptiveTest { function testRemoveLiquidity() public { testAddLiquidity(); + // Simulate passage of cooldown period + vm.warp(block.timestamp + 14 days); + (uint128 amount, uint128 rewardDebt, uint128 token0FeeDebt, uint128 token1FeeDebt) = foldCaptiveStaking.balances(User01); @@ -212,6 +215,9 @@ contract UnitTests is BaseCaptiveTest { assertEq(rewardDebt, foldCaptiveStaking.rewardsPerLiquidity()); assertGt(weth.balanceOf(User01), initialBalance); + // Simulate passage of cooldown period + vm.warp(block.timestamp + 14 days); + (uint128 liq,,,) = foldCaptiveStaking.balances(User01); foldCaptiveStaking.withdraw(liq / 3); } @@ -235,21 +241,6 @@ contract UnitTests is BaseCaptiveTest { vm.stopPrank(); } - function testProRataWithdrawals() public { - testAddLiquidity(); - - (uint128 liq,,,) = foldCaptiveStaking.balances(User01); - - // Attempt to withdraw more than allowed amount - vm.expectRevert(WithdrawProRata.selector); - foldCaptiveStaking.withdraw(liq); - - // Pro-rated withdrawal - foldCaptiveStaking.withdraw(liq / 2); - (uint128 amount,,,) = foldCaptiveStaking.balances(User01); - assertEq(amount, liq / 2); - } - function testZeroDeposit() public { vm.expectRevert(); foldCaptiveStaking.deposit(0, 0, 0); @@ -268,6 +259,45 @@ contract UnitTests is BaseCaptiveTest { vm.expectRevert(); attack.attack(); } + + function testMinimumDeposit() public { + fold.transfer(User01, 0.5 ether); + + vm.deal(User01, 0.5 ether); + vm.startPrank(User01); + + weth.deposit{value: 0.5 ether}(); + weth.approve(address(foldCaptiveStaking), type(uint256).max); + fold.approve(address(foldCaptiveStaking), type(uint256).max); + + // Expect revert due to minimum deposit requirement + vm.expectRevert(DepositAmountBelowMinimum.selector); + foldCaptiveStaking.deposit(0.5 ether, 0.5 ether, 0); + + vm.stopPrank(); + } + + function testWithdrawalCooldown() public { + testAddLiquidity(); + + vm.startPrank(User01); + + (uint128 liq,,,) = foldCaptiveStaking.balances(User01); + + // Attempt to withdraw before cooldown period + vm.expectRevert(WithdrawalCooldownPeriodNotMet.selector); + foldCaptiveStaking.withdraw(liq / 2); + + // Simulate passage of cooldown period + vm.warp(block.timestamp + 14 days); + + // Withdraw after cooldown period + foldCaptiveStaking.withdraw(liq / 2); + (uint128 amount,,,) = foldCaptiveStaking.balances(User01); + assertEq(amount, liq / 2); + + vm.stopPrank(); + } } // Reentrancy attack contract