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

feat: burn 1e3 shares for first mint into BinPool #212

Merged
merged 11 commits into from
Nov 14, 2024
2 changes: 1 addition & 1 deletion .forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
178130
196277
2 changes: 1 addition & 1 deletion .forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
311254
311574
2 changes: 1 addition & 1 deletion .forge-snapshots/BinMintBurnFeeHookTest#test_Burn.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
170145
184559
2 changes: 1 addition & 1 deletion .forge-snapshots/BinMintBurnFeeHookTest#test_Mint.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
410336
410526
2 changes: 1 addition & 1 deletion .forge-snapshots/BinPoolManagerBytecodeSize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
23287
23389
Original file line number Diff line number Diff line change
@@ -1 +1 @@
133892
146654
Original file line number Diff line number Diff line change
@@ -1 +1 @@
142717
142933
Original file line number Diff line number Diff line change
@@ -1 +1 @@
289683
287239
2 changes: 1 addition & 1 deletion .forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
127065
138120
Original file line number Diff line number Diff line change
@@ -1 +1 @@
968475
971323
Original file line number Diff line number Diff line change
@@ -1 +1 @@
327787
330068
Original file line number Diff line number Diff line change
@@ -1 +1 @@
337511
337831
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140062
140319
Original file line number Diff line number Diff line change
@@ -1 +1 @@
304550
304870
24 changes: 20 additions & 4 deletions src/pool-bin/libraries/BinPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ library BinPool {
mapping(bytes32 => bytes32) level2;
}

/// @dev when a bin has supply for the first time, 1e3 share will be locked up
/// this is to prevent share inflation attack on BinPool type
uint256 constant MINIMUM_SHARE = 1e3;

function initialize(State storage self, uint24 activeId, uint24 protocolFee, uint24 lpFee) internal {
/// An initialized pool will not have activeId: 0
if (self.slot0.activeId() != 0) revert PoolAlreadyInitialized();
Expand Down Expand Up @@ -385,12 +389,12 @@ library BinPool {
amountsLeft = amountsLeft.sub(amountsIn);
feeAmountToProtocol = feeAmountToProtocol.add(binFeeAmt);

shares = _addShare(self, params.to, id, params.salt, shares);

arrays.ids[i] = id;
arrays.amounts[i] = amountsInToBin;
arrays.liquidityMinted[i] = shares;
ChefMist marked this conversation as resolved.
Show resolved Hide resolved

_addShare(self, params.to, id, params.salt, shares);

compositionFeeAmount = compositionFeeAmount.add(binCompositionFee);

unchecked {
Expand Down Expand Up @@ -467,8 +471,20 @@ library BinPool {
}

/// @notice Add share to user's position and update total share supply of bin
function _addShare(State storage self, address owner, uint24 binId, bytes32 salt, uint256 shares) internal {
self.positions.get(owner, binId, salt).addShare(shares);
/// @dev if bin is empty, deduct MINIMUM_SHARE from shares
/// @return userShareAdded The amount of share added to user's position
function _addShare(State storage self, address owner, uint24 binId, bytes32 salt, uint256 shares)
internal
returns (uint256 userShareAdded)
{
userShareAdded = shares;
if (self.shareOfBin[binId] == 0) {
/// @dev Only for first liquidity provider for the bin, deduct MINIMUM_SHARE, expected to underflow
/// if shares < MINIMUM_SHARE for first mint
userShareAdded = shares - MINIMUM_SHARE;
}

self.positions.get(owner, binId, salt).addShare(userShareAdded);
self.shareOfBin[binId] += shares;
}

Expand Down
10 changes: 5 additions & 5 deletions test/pool-bin/BinHookReturnsDelta.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ contract BinHookReturnsDelta is Test, GasSnapshot, BinTestHelper {
binLiquidityHelper.burn(key, burnParams, "");

(uint128 reserveXAfter, uint128 reserveYAfter,,) = poolManager.getBin(key.toId(), activeId);

assertEq(reserveXAfter, 0);
assertEq(reserveYAfter, 0);
assertEq(token0.balanceOf(address(binReturnsDeltaHook)), 0.1 ether);
assertEq(token1.balanceOf(address(binReturnsDeltaHook)), 0.1 ether);
// reserve non zero due to min liquidity (1e3) locked up in the bin
assertEq(reserveXAfter, 1);
assertEq(reserveYAfter, 1);
assertEq(token0.balanceOf(address(binReturnsDeltaHook)), 0.1 ether - 1);
assertEq(token1.balanceOf(address(binReturnsDeltaHook)), 0.1 ether - 1);
}

function testSwap_noSwap_specifyInput() external {
Expand Down
20 changes: 12 additions & 8 deletions test/pool-bin/BinMintBurnFeeHook.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,25 @@ contract BinMintBurnFeeHookTest is Test, GasSnapshot, BinTestHelper {
assertEq(vault.balanceOf(address(binMintBurnFeeHook), key.currency0), 2 ether);
assertEq(vault.balanceOf(address(binMintBurnFeeHook), key.currency1), 2 ether);

// take 4x the burn amount as fee
IBinPoolManager.BurnParams memory burnParams =
_getSingleBinBurnLiquidityParams(key, poolManager, activeId, address(binLiquidityHelper), 100);
snapStart("BinMintBurnFeeHookTest#test_Burn");
binLiquidityHelper.burn(key, burnParams, "");
snapEnd();

// +1 from remove liqudiity, -4 from hook fee
assertEq(token0.balanceOf(address(this)), 7 ether + 1 ether - 4 ether);
assertEq(token1.balanceOf(address(this)), 7 ether + 1 ether - 4 ether);
// +1 eth from remove liqudiity, -4 eth from hook fee
// +3 from min_liquidity amount as -1 (min_liquidity) + 1 * 4 (fee)
assertEq(token0.balanceOf(address(this)), 7 ether + 1 ether - 4 ether + 3);
assertEq(token1.balanceOf(address(this)), 7 ether + 1 ether - 4 ether + 3);

// -1 from remove liquidity, +4 from hook calling vault.mint
assertEq(token0.balanceOf(address(vault)), 3 ether - 1 ether + 4 ether);
assertEq(token1.balanceOf(address(vault)), 3 ether - 1 ether + 4 ether);
assertEq(vault.balanceOf(address(binMintBurnFeeHook), key.currency0), 2 ether + 4 ether);
assertEq(vault.balanceOf(address(binMintBurnFeeHook), key.currency1), 2 ether + 4 ether);
// -1 eth from remove liquidity, +4 eth from hook calling vault.mint
assertEq(token0.balanceOf(address(vault)), 3 ether - 1 ether + 4 ether - 3);
assertEq(token1.balanceOf(address(vault)), 3 ether - 1 ether + 4 ether - 3);

// -4 as due to min_liquidity = 1, hook took 4 token less fee
assertEq(vault.balanceOf(address(binMintBurnFeeHook), key.currency0), 2 ether + 4 ether - 4);
assertEq(vault.balanceOf(address(binMintBurnFeeHook), key.currency1), 2 ether + 4 ether - 4);
}

receive() external payable {}
Expand Down
37 changes: 25 additions & 12 deletions test/pool-bin/BinPoolManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,17 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper {
for (uint256 i = 0; i < binIds.length; i++) {
(uint128 binReserveX, uint128 binReserveY,,) = poolManager.getBin(key.toId(), binIds[i]);

// make sure the liquidity is added to the correct bin
assertEq(binReserveX, 0 ether);
assertEq(binReserveY, 0 ether);
// should have 1 token left due to min liquidity
if (binIds[i] < activeId) {
assertEq(binReserveX, 0);
assertEq(binReserveY, 1);
} else if (binIds[i] > activeId) {
assertEq(binReserveX, 1);
assertEq(binReserveY, 0);
} else {
assertEq(binReserveX, 1);
assertEq(binReserveY, 1);
}

BinPosition.Info memory position =
poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt);
Expand All @@ -480,6 +488,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper {
token0.mint(address(this), 30 ether);
token1.mint(address(this), 30 ether);

// mint for salt1
(IBinPoolManager.MintParams memory mintParams, uint24[] memory binIds) =
_getMultipleBinMintParams(activeId, 2 ether, 2 ether, 5, 5, salt1);
binLiquidityHelper.mint(key, mintParams, "");
Expand Down Expand Up @@ -514,6 +523,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper {
}

{
// now mint for salt2
(mintParams, binIds) = _getMultipleBinMintParams(activeId, 2 ether, 2 ether, 5, 5, salt2);
binLiquidityHelper.mint(key, mintParams, "");

Expand Down Expand Up @@ -542,11 +552,13 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper {
// only position with salt 0 should be empty
assertTrue(position0.share == 0);
assertTrue(position1.share != 0);
assertTrue(position1.share == position2.share);
// // 1e3 is MINIMUM_SHARE locked when added liquidity first
assertTrue(position1.share + 1e3 == position2.share);
}
}

{
// now mint for salt0
(mintParams, binIds) = _getMultipleBinMintParams(activeId, 2 ether, 2 ether, 5, 5, salt0);
binLiquidityHelper.mint(key, mintParams, "");

Expand All @@ -572,9 +584,10 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper {
BinPosition.Info memory position2 =
poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt2);

// 1e3 is MINIMUM_SHARE locked when added liquidity first
assertTrue(position0.share != 0);
assertTrue(position1.share == position0.share);
assertTrue(position1.share == position2.share);
assertTrue(position1.share + 1e3 == position0.share);
assertTrue(position1.share + 1e3 == position2.share);
}
}

Expand All @@ -589,13 +602,13 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper {
// make sure the liquidity is added to the correct bin
if (binIds[i] < activeId) {
assertEq(binReserveX, 0 ether);
assertEq(binReserveY, 0.4 ether * 2);
assertEq(binReserveY, 0.4 ether * 2 + 1);
} else if (binIds[i] > activeId) {
assertEq(binReserveX, 0.4 ether * 2);
assertEq(binReserveX, 0.4 ether * 2 + 1);
assertEq(binReserveY, 0 ether);
} else {
assertEq(binReserveX, 0.4 ether * 2);
assertEq(binReserveY, 0.4 ether * 2);
assertEq(binReserveX, 0.4 ether * 2 + 1);
assertEq(binReserveY, 0.4 ether * 2 + 1);
}

BinPosition.Info memory position0 =
Expand Down Expand Up @@ -628,7 +641,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper {
uint256[] memory ids = new uint256[](1);
bytes32[] memory amounts = new bytes32[](1);
ids[0] = activeId;
amounts[0] = uint128(1e18).encode(uint128(1e18));
amounts[0] = uint128(1e18 - 1).encode(uint128(1e18 - 1)); // -1 due to minshare locked up
vm.expectEmit();
emit IBinPoolManager.Burn(key.toId(), address(binLiquidityHelper), ids, 0, amounts);

Expand Down Expand Up @@ -697,7 +710,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper {
uint256[] memory ids = new uint256[](1);
bytes32[] memory amounts = new bytes32[](1);
ids[0] = activeId;
amounts[0] = uint128(1e18).encode(uint128(1e18));
amounts[0] = uint128(1e18 - 1).encode(uint128(1e18 - 1)); // -1 due to minshare locked up
vm.expectEmit();
emit IBinPoolManager.Burn(key.toId(), address(binLiquidityHelper), ids, 0, amounts);

Expand Down
18 changes: 15 additions & 3 deletions test/pool-bin/BinPoolManagerBehaviorComparison.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ abstract contract LBFuzzer is LBHelper, BinTestHelper {
Currency currency0;
Currency currency1;

/// @dev when a bin has supply for the first time, 1e3 share will be locked up
uint256 constant MINIMUM_SHARE = 1e3;

function setUp() public virtual override {
super.setUp();

Expand Down Expand Up @@ -135,9 +138,18 @@ abstract contract LBFuzzer is LBHelper, BinTestHelper {
for (uint256 i = 0; i < liquidityMinted.length; i++) {
uint24 id = uint24(uint256(mintParams.liquidityConfigs[i]));
BinPosition.Info memory positionInfoAfter = manager.getPosition(key.toId(), address(liquidityHelper), id, 0);
assertEq(
liquidityMinted[i], positionInfoAfter.share - sharesBefore[i], "Expecting to mint same liquidity !"
);
if (sharesBefore[i] == 0) {
// first mint, so positionInfoAfter.share has MINIMUM_SHARE lesser
assertEq(
liquidityMinted[i],
positionInfoAfter.share - sharesBefore[i] + MINIMUM_SHARE,
"Expecting to mint same liquidity !"
);
} else {
assertEq(
liquidityMinted[i], positionInfoAfter.share - sharesBefore[i], "Expecting to mint same liquidity !"
);
}
}
}

Expand Down
4 changes: 0 additions & 4 deletions test/pool-bin/helpers/BinMintBurnFeeHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@ contract BinMintBurnFeeHook is BaseBinTestHook {
BalanceDelta delta,
bytes calldata
) external override returns (bytes4, BalanceDelta) {
console2.log("afterBurn delta");
console2.logInt(delta.amount0());
console2.logInt(delta.amount1());

int128 amt0Fee;
if (delta.amount0() > 0) {
amt0Fee = (delta.amount0()) * 4;
Expand Down
23 changes: 15 additions & 8 deletions test/pool-bin/libraries/BinPoolDonate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,22 @@ contract BinPoolDonateTest is BinTestHelper {
}

function testDonate_InsufficientBinShareForDonate(uint256 remainingShare) public {
uint256 MINIMUM_SHARE = 1e3;

// Initialize pool and add liqudiity
poolManager.initialize(key, activeId);
addLiquidityToBin(key, poolManager, alice, activeId, 1e18, 1e18, 1e18, 1e18, "");

// Remove all share leaving less than MIN_LIQUIDITY_BEFORE_DONATE shares
remainingShare = bound(remainingShare, 1, poolManager.minBinShareForDonate() - 1);
remainingShare = bound(remainingShare, 1, poolManager.minBinShareForDonate() - 1 - MINIMUM_SHARE);
uint256 aliceShare = poolManager.getPosition(poolId, alice, activeId, 0).share;
removeLiquidityFromBin(key, poolManager, alice, activeId, aliceShare - remainingShare, "");

vm.expectRevert(abi.encodeWithSelector(IBinPoolManager.InsufficientBinShareForDonate.selector, remainingShare));
vm.expectRevert(
abi.encodeWithSelector(
IBinPoolManager.InsufficientBinShareForDonate.selector, remainingShare + MINIMUM_SHARE
)
);
poolManager.donate(key, 1e18, 1e18, "");
}

Expand Down Expand Up @@ -98,16 +104,17 @@ contract BinPoolDonateTest is BinTestHelper {
assertEq(removeDelta1.amount0(), 2e18);
assertEq(removeDelta1.amount1(), 2e18);

// lesser than 2e18 as alice is the first lp provider and liquidity locked up
BalanceDelta removeDelta2 = removeLiquidityFromBin(key, poolManager, alice, activeId, aliceShare, "");
assertEq(removeDelta2.amount0(), 2e18);
assertEq(removeDelta2.amount1(), 2e18);
assertEq(removeDelta2.amount0(), 2e18 - 1);
assertEq(removeDelta2.amount1(), 2e18 - 1);

// Verify no reserve remaining
// Verify only min_liquidity worth of token locked up
(reserveX, reserveY,,) = poolManager.getBin(poolId, activeId);
assertEq(reserveX, 0);
assertEq(reserveY, 0);
assertEq(reserveX, 1);
assertEq(reserveY, 1);

vm.expectRevert(abi.encodeWithSelector(IBinPoolManager.InsufficientBinShareForDonate.selector, 0));
vm.expectRevert(abi.encodeWithSelector(IBinPoolManager.InsufficientBinShareForDonate.selector, 1e3));
poolManager.donate(key, 1e18, 1e18, "");
}

Expand Down
Loading
Loading