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

FullRange Hook Bug Fixes #128

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .forge-snapshots/FullRangeAddInitialLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
311181
311621
2 changes: 1 addition & 1 deletion .forge-snapshots/FullRangeAddLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
122990
125693
2 changes: 1 addition & 1 deletion .forge-snapshots/FullRangeInitialize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1015181
1015209
2 changes: 1 addition & 1 deletion .forge-snapshots/FullRangeRemoveLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
110566
110668
2 changes: 1 addition & 1 deletion .forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
240044
240370
24 changes: 15 additions & 9 deletions contracts/hooks/examples/FullRange.sol
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ contract FullRange is BaseHook {
if (poolLiquidity == 0 && liquidity <= MINIMUM_LIQUIDITY) {
revert LiquidityDoesntMeetMinimum();
}

BalanceDelta addedDelta = modifyLiquidity(
key,
IPoolManager.ModifyLiquidityParams({
Expand All @@ -157,12 +158,14 @@ contract FullRange is BaseHook {

if (poolLiquidity == 0) {
// permanently lock the first MINIMUM_LIQUIDITY tokens
liquidity -= MINIMUM_LIQUIDITY;
UniswapV4ERC20(pool.liquidityToken).mint(address(0), MINIMUM_LIQUIDITY);
UniswapV4ERC20(pool.liquidityToken).mint(params.to, liquidity - MINIMUM_LIQUIDITY);
} else {
uint256 liquidityMinted = uint256(liquidity) * UniswapV4ERC20(pool.liquidityToken).totalSupply()
/ uint256(manager.getLiquidity(poolId) - liquidity);
UniswapV4ERC20(pool.liquidityToken).mint(params.to, liquidityMinted);
}

UniswapV4ERC20(pool.liquidityToken).mint(params.to, liquidity);

if (uint128(-addedDelta.amount0()) < params.amount0Min || uint128(-addedDelta.amount1()) < params.amount1Min) {
revert TooMuchSlippage();
}
Expand Down Expand Up @@ -206,6 +209,7 @@ contract FullRange is BaseHook {
function beforeInitialize(address, PoolKey calldata key, uint160, bytes calldata)
external
override
onlyByManager
returns (bytes4)
{
if (key.tickSpacing != 60) revert TickSpacingNotDefault();
Expand Down Expand Up @@ -280,10 +284,6 @@ contract FullRange is BaseHook {
PoolId poolId = key.toId();
PoolInfo storage pool = poolInfo[poolId];

if (pool.hasAccruedFees) {
_rebalance(key);
}

uint256 liquidityToRemove = FullMath.mulDiv(
uint256(-params.liquidityDelta),
manager.getLiquidity(poolId),
Expand All @@ -292,13 +292,19 @@ contract FullRange is BaseHook {

params.liquidityDelta = -(liquidityToRemove.toInt256());
(delta,) = manager.modifyLiquidity(key, params, ZERO_BYTES);
pool.hasAccruedFees = false;
}

function _unlockCallback(bytes calldata rawData) internal override returns (bytes memory) {
CallbackData memory data = abi.decode(rawData, (CallbackData));
BalanceDelta delta;

PoolId poolId = data.key.toId();
PoolInfo storage pool = poolInfo[data.key.toId()];
if (pool.hasAccruedFees) {
pool.hasAccruedFees = false;
rebalance(data.key);
}

if (data.params.liquidityDelta < 0) {
delta = _removeLiquidity(data.key, data.params);
_takeDeltas(data.sender, data.key, delta);
Expand All @@ -309,7 +315,7 @@ contract FullRange is BaseHook {
return abi.encode(delta);
}

function _rebalance(PoolKey memory key) public {
function rebalance(PoolKey memory key) public {
PoolId poolId = key.toId();
(BalanceDelta balanceDelta,) = manager.modifyLiquidity(
key,
Expand Down
56 changes: 53 additions & 3 deletions test/FullRange.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {console} from "forge-std/console.sol";

contract TestFullRange is Test, Deployers, GasSnapshot {
using PoolIdLibrary for PoolKey;
Expand Down Expand Up @@ -294,9 +295,8 @@ contract TestFullRange is Test, Deployers, GasSnapshot {
(hasAccruedFees,) = fullRange.poolInfo(id);
liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this));

assertEq(manager.getLiquidity(id), liquidityTokenBal + LOCKED_LIQUIDITY);
assertEq(liquidityTokenBal, 14546694553059925434 - LOCKED_LIQUIDITY);
assertEq(hasAccruedFees, true);
assertTrue(manager.getLiquidity(id) > liquidityTokenBal + LOCKED_LIQUIDITY);
assertEq(hasAccruedFees, false);
}

function testFullRange_addLiquidity_FailsIfTooMuchSlippage() public {
Expand Down Expand Up @@ -765,6 +765,56 @@ contract TestFullRange is Test, Deployers, GasSnapshot {
);
}

function testFullRange_LostFunds() public {
PoolKey memory testKey = key;
manager.initialize(testKey, SQRT_PRICE_1_1, ZERO_BYTES);

console.log(key.currency0.balanceOf(address(manager)), key.currency1.balanceOf(address(manager)));

uint128 liquidity = fullRange.addLiquidity(
FullRange.AddLiquidityParams(
key.currency0, key.currency1, 3000, 10 ether, 10 ether, 9 ether, 9 ether, address(this), MAX_DEADLINE
)
);
console.log(liquidity);

console.log(key.currency0.balanceOf(address(manager)), key.currency1.balanceOf(address(manager)));

IPoolManager.SwapParams memory params =
IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_PRICE_1_2});
HookEnabledSwapRouter.TestSettings memory settings =
HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false});
IPoolManager.SwapParams memory params2 =
IPoolManager.SwapParams({zeroForOne: false, amountSpecified: -1 ether, sqrtPriceLimitX96: SQRT_PRICE_2_1});

console.log(key.currency0.balanceOf(address(manager)), key.currency1.balanceOf(address(manager)));
for (uint256 i; i < 100; ++i) {
router.swap(testKey, params, settings, ZERO_BYTES);
router.swap(testKey, params2, settings, ZERO_BYTES);
}
console.log("done farming swap fees");
console.log(key.currency0.balanceOf(address(manager)), key.currency1.balanceOf(address(manager)));

liquidity = fullRange.addLiquidity(
FullRange.AddLiquidityParams(
key.currency0, key.currency1, 3000, 0.7 ether, 0.7 ether, 0, 0, address(this), MAX_DEADLINE
)
);
console.log(liquidity);
{
FullRange.RemoveLiquidityParams memory removeLiquidityParams =
FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 3000, 1, MAX_DEADLINE);
fullRange.removeLiquidity(removeLiquidityParams);
}

console.log(key.currency0.balanceOf(address(manager)), key.currency1.balanceOf(address(manager)));

FullRange.RemoveLiquidityParams memory removeLiquidityParams =
FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 3000, 9999999999999998000, MAX_DEADLINE);
fullRange.removeLiquidity(removeLiquidityParams);
console.log(key.currency0.balanceOf(address(manager)), key.currency1.balanceOf(address(manager)));
}

function createPoolKey(MockERC20 tokenA, MockERC20 tokenB) internal view returns (PoolKey memory) {
if (address(tokenA) > address(tokenB)) (tokenA, tokenB) = (tokenB, tokenA);
return PoolKey(Currency.wrap(address(tokenA)), Currency.wrap(address(tokenB)), 3000, TICK_SPACING, fullRange);
Expand Down
Loading