Skip to content

Commit

Permalink
feat: 14 day cooldown withdraw, deposit min (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
sandybradley authored Aug 25, 2024
1 parent 193fa60 commit e60e93e
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 64 deletions.
24 changes: 15 additions & 9 deletions src/FoldCaptiveStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -207,6 +217,8 @@ contract FoldCaptiveStaking is Owned(msg.sender) {
revert DepositCapReached();
}

depositTimeStamp[msg.sender] = block.timestamp;

emit Deposit(msg.sender, amount0, amount1);
}

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/BaseCaptiveTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ contract BaseCaptiveTest is Test {
error NotInitialized();
error ZeroLiquidity();
error WithdrawFailed();
error DepositAmountBelowMinimum();
error WithdrawalCooldownPeriodNotMet();
error WithdrawProRata();
error DepositCapReached();

Expand Down
85 changes: 30 additions & 55 deletions test/UnitTests.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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);

Expand Down Expand Up @@ -218,6 +221,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);
}
Expand All @@ -242,23 +248,6 @@ contract UnitTests is BaseCaptiveTest {
vm.stopPrank();
}

/// @dev Ensure pro-rata withdrawals are handled correctly
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);
}

/// @dev Ensure zero deposits are handled correctly and revert as expected.
function testZeroDeposit() public {
vm.expectRevert();
foldCaptiveStaking.deposit(0, 0, 0);
Expand All @@ -279,6 +268,21 @@ contract UnitTests is BaseCaptiveTest {
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);
}

/// @dev Deposit Cap Enforcement: Test to ensure the deposit cap is respected.
function testDepositCap() public {
uint256 cap = 100 ether;
Expand All @@ -303,52 +307,23 @@ contract UnitTests is BaseCaptiveTest {
vm.stopPrank();
}

/// @dev Multiple Users: Test simultaneous deposits and withdrawals by multiple users.
function testMultipleUsersDepositWithdraw() public {
// User 1 deposits
fold.transfer(User01, 1_000 ether);
vm.deal(User01, 1_000 ether);
vm.startPrank(User01);

weth.deposit{value: 1_000 ether}();
weth.approve(address(foldCaptiveStaking), type(uint256).max);
fold.approve(address(foldCaptiveStaking), type(uint256).max);

foldCaptiveStaking.deposit(1_000 ether, 1_000 ether, 0);

vm.stopPrank();

// User 2 deposits
fold.transfer(User02, 500 ether);
vm.deal(User02, 500 ether);
vm.startPrank(User02);

weth.deposit{value: 500 ether}();
weth.approve(address(foldCaptiveStaking), type(uint256).max);
fold.approve(address(foldCaptiveStaking), type(uint256).max);

foldCaptiveStaking.deposit(500 ether, 500 ether, 0);

vm.stopPrank();
function testWithdrawalCooldown() public {
testAddLiquidity();

// User 1 withdraws
vm.startPrank(User01);

(uint128 liq,,,) = foldCaptiveStaking.balances(User01);
foldCaptiveStaking.withdraw(liq / 2);

(uint128 amount,,,) = foldCaptiveStaking.balances(User01);
assertEq(amount, liq / 2);

vm.stopPrank();
// Attempt to withdraw before cooldown period
vm.expectRevert(WithdrawalCooldownPeriodNotMet.selector);
foldCaptiveStaking.withdraw(liq / 2);

// User 2 withdraws
vm.startPrank(User02);
// Simulate passage of cooldown period
vm.warp(block.timestamp + 14 days);

(liq,,,) = foldCaptiveStaking.balances(User02);
// Withdraw after cooldown period
foldCaptiveStaking.withdraw(liq / 2);

(amount,,,) = foldCaptiveStaking.balances(User02);
(uint128 amount,,,) = foldCaptiveStaking.balances(User01);
assertEq(amount, liq / 2);

vm.stopPrank();
Expand Down

0 comments on commit e60e93e

Please sign in to comment.