Skip to content

Commit

Permalink
feat: 14 day cooldown withdraw, deposit min
Browse files Browse the repository at this point in the history
  • Loading branch information
sandybradley committed Jul 29, 2024
1 parent 9acb1b7 commit d5f2941
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 25 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
3 changes: 2 additions & 1 deletion test/BaseCaptiveTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ contract BaseCaptiveTest is Test {
error NotInitialized();
error ZeroLiquidity();
error WithdrawFailed();
error WithdrawProRata();
error DepositAmountBelowMinimum();
error WithdrawalCooldownPeriodNotMet();

FoldCaptiveStaking public foldCaptiveStaking;

Expand Down
60 changes: 45 additions & 15 deletions test/UnitTests.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

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

0 comments on commit d5f2941

Please sign in to comment.