diff --git a/.forge-snapshots/add liquidity.snap b/.forge-snapshots/add liquidity.snap index e4e69e1d..fab7a85a 100644 --- a/.forge-snapshots/add liquidity.snap +++ b/.forge-snapshots/add liquidity.snap @@ -1 +1 @@ -440321 \ No newline at end of file +466859 \ No newline at end of file diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index 84f823ca..049f9d21 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -22,7 +22,7 @@ import {FixedPoint128} from "@uniswap/v4-core/contracts/libraries/FixedPoint128. import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {ILockCallback} from "@uniswap/v4-core/contracts/interfaces/callback/ILockCallback.sol"; -import {IERC20Metadata} from "../../interfaces/IERC20Metadata.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import "../../libraries/LiquidityAmounts.sol"; @@ -42,6 +42,7 @@ contract FullRange is BaseHook, ILockCallback { int24 internal constant MAX_TICK = -MIN_TICK; int256 internal constant MAX_INT = type(int256).max; + uint16 internal constant MINIMUM_LIQUIDITY = 10 ** 3; struct CallbackData { address sender; @@ -99,6 +100,10 @@ contract FullRange is BaseHook, ILockCallback { if (sqrtPriceX96 == 0) revert PoolNotInitialized(); + PoolInfo storage pool = poolInfo[poolId]; + + uint128 poolLiquidity = poolManager.getLiquidity(poolId); + liquidity = LiquidityAmounts.getLiquidityForAmounts( sqrtPriceX96, TickMath.getSqrtRatioAtTick(MIN_TICK), @@ -115,7 +120,11 @@ contract FullRange is BaseHook, ILockCallback { liquidityDelta: int256(int128(liquidity)) }) ); - PoolInfo storage pool = poolInfo[poolId]; + + if (poolLiquidity == 0) { + liquidity -= MINIMUM_LIQUIDITY; + UniswapV4ERC20(pool.liquidityToken).mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens + } UniswapV4ERC20(pool.liquidityToken).mint(to, liquidity); } diff --git a/contracts/interfaces/IERC20Metadata.sol b/contracts/interfaces/IERC20Metadata.sol deleted file mode 100644 index fa82839f..00000000 --- a/contracts/interfaces/IERC20Metadata.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity ^0.8.19; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -/// @title IERC20Metadata -/// @title Interface for ERC20 Metadata -/// @notice Extension to IERC20 that includes token metadata -interface IERC20Metadata is IERC20 { - /// @return The name of the token - function name() external view returns (string memory); - - /// @return The symbol of the token - function symbol() external view returns (string memory); - - /// @return The number of decimal places the token has - function decimals() external view returns (uint8); -} diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index da70cf1f..5295a08b 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -147,7 +147,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { (, address liquidityToken) = fullRange.poolInfo(id); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether - 1000); (bool owed,) = fullRange.poolInfo(id); assertEq(owed, false); @@ -171,7 +171,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { (, address liquidityToken) = fullRange.poolInfo(id); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 25 ether + 1); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 25 ether + 1 - 1000); (bool owed,) = fullRange.poolInfo(id); assertEq(owed, false); @@ -187,7 +187,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { (, address liquidityToken) = fullRange.poolInfo(id); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether - 1000); assertEq(MockERC20(token0).balanceOf(address(this)), prevBalance0 - 10 ether); assertEq(MockERC20(token0).balanceOf(address(this)), prevBalance1 - 10 ether); @@ -212,7 +212,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { fullRange.addLiquidity(address(token0), address(token1), 3000, 5 ether, 5 ether, address(this), MAX_DEADLINE); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 14546694553059925434); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 14546694553059925434 - 1000); (owed,) = fullRange.poolInfo(id); assertEq(owed, true); @@ -279,7 +279,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { (, address liquidityToken) = fullRange.poolInfo(id); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether - 1000); assertEq(MockERC20(token0).balanceOf(address(this)), prevBalance0 - 10 ether); assertEq(MockERC20(token1).balanceOf(address(this)), prevBalance1 - 10 ether); @@ -290,7 +290,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { fullRange.removeLiquidity(address(token0), address(token1), 3000, 1 ether, MAX_DEADLINE); snapEnd(); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 9 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 9 ether - 1000); assertEq(MockERC20(token0).balanceOf(address(this)), prevBalance0 - 9 ether - 1); assertEq(MockERC20(token1).balanceOf(address(this)), prevBalance1 - 9 ether - 1); @@ -323,7 +323,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { (, address liquidityToken) = fullRange.poolInfo(id); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether - 1000); assertEq(MockERC20(token0).balanceOf(address(this)), prevBalance0 - 10 ether); assertEq(MockERC20(token1).balanceOf(address(this)), prevBalance1 - 10 ether); @@ -332,7 +332,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { fullRange.removeLiquidity(address(token0), address(token1), 3000, 5 ether, MAX_DEADLINE); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 5 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 5 ether - 1000); assertEq(MockERC20(token0).balanceOf(address(this)), prevBalance0 - 5 ether - 1); assertEq(MockERC20(token1).balanceOf(address(this)), prevBalance1 - 5 ether - 1); @@ -353,14 +353,14 @@ contract TestFullRange is Test, Deployers, GasSnapshot { (, address liquidityToken) = fullRange.poolInfo(id); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether - 1000); fullRange.addLiquidity(address(token0), address(token1), 3000, 5 ether, 2.5 ether, address(this), MAX_DEADLINE); assertEq(MockERC20(token0).balanceOf(address(this)), prevBalance0 - 12.5 ether); assertEq(MockERC20(token1).balanceOf(address(this)), prevBalance1 - 12.5 ether); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 12.5 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 12.5 ether - 1000); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -369,7 +369,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { assertEq(MockERC20(token0).balanceOf(address(this)), prevBalance0 - 7.5 ether - 1); assertEq(MockERC20(token1).balanceOf(address(this)), prevBalance1 - 7.5 ether - 1); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 7.5 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 7.5 ether - 1000); } function testSwapRemoveLiquiditySucceedsWithRebalance() public { @@ -379,7 +379,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { (, address liquidityToken) = fullRange.poolInfo(id); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether); + assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether - 1000); IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2});