From c64e82e108b478ce506418d90e74c115557e51d7 Mon Sep 17 00:00:00 2001 From: Tina Zheng Date: Wed, 29 Nov 2023 16:27:10 -0500 Subject: [PATCH] move addLiquidity functions to liquiditymanagement --- contracts/NonfungiblePositionManagerV4.sol | 2 - contracts/base/LiquidityManagement.sol | 88 ++++++++++++++++--- contracts/interfaces/ILiquidityManagement.sol | 39 +------- .../INonfungiblePositionManagerV4.sol | 46 ++++++++-- 4 files changed, 115 insertions(+), 60 deletions(-) diff --git a/contracts/NonfungiblePositionManagerV4.sol b/contracts/NonfungiblePositionManagerV4.sol index fd74569a..3dd2e447 100644 --- a/contracts/NonfungiblePositionManagerV4.sol +++ b/contracts/NonfungiblePositionManagerV4.sol @@ -22,7 +22,6 @@ contract NonfungiblePositionManagerV4 is ERC721, PeripheryImmutableState, PeripheryValidation, - PeripheryPayments, LiquidityManagement, SelfPermit, Multicall @@ -130,7 +129,6 @@ contract NonfungiblePositionManagerV4 is returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) { // TODO: implement this - // will do something like return mintEntry(params) } /// @inheritdoc INonfungiblePositionManagerV4 diff --git a/contracts/base/LiquidityManagement.sol b/contracts/base/LiquidityManagement.sol index 540a8096..90f8c82b 100644 --- a/contracts/base/LiquidityManagement.sol +++ b/contracts/base/LiquidityManagement.sol @@ -3,12 +3,33 @@ pragma solidity ^0.8.19; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {ILockCallback} from "@uniswap/v4-core/contracts/interfaces/callback//ILockCallback.sol"; -import {IPeripheryPayments} from "../interfaces/IPeripheryPayments.sol"; -import {ILiquidityManagement} from "../interfaces/ILiquidityManagement.sol"; +import {ILockCallback} from "@uniswap/v4-core/contracts/interfaces/callback/ILockCallback.sol"; +import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; + +import {LiquidityAmounts} from "../libraries/LiquidityAmounts.sol"; import {PeripheryImmutableState} from "./PeripheryImmutableState.sol"; +import {PeripheryPayments} from "./PeripheryPayments.sol"; + +/// @title Liquidity management functions +/// @notice Internal functions for safely managing liquidity in Uniswap V4 +abstract contract LiquidityManagement is ILockCallback, PeripheryImmutableState, PeripheryPayments { + using CurrencyLibrary for Currency; + using PoolIdLibrary for PoolKey; + + error PriceSlippage(); + + enum CallbackType {AddLiquidity} + + struct CallbackData { + CallbackType callbackType; + address sender; + bytes params; + } -abstract contract LiquidityManagement is ILockCallback, ILiquidityManagement, PeripheryImmutableState { struct AddLiquidityParams { PoolKey poolKey; int24 tickLower; @@ -20,20 +41,63 @@ abstract contract LiquidityManagement is ILockCallback, ILiquidityManagement, Pe bytes hookData; } - function mintEntry(MintParams memory params) + /// @notice Add liquidity to an initialized pool + function addLiquidity(AddLiquidityParams memory params) internal - returns (uint256 tokenId, uint128 liquidity, BalanceDelta delta) + returns (uint128 liquidity, uint256 amount0, uint256 amount1) { - // TODO: poolManager.lock call here + (liquidity, amount0, amount1) = abi.decode( + poolManager.lock(abi.encode(CallbackData(CallbackType.AddLiquidity, msg.sender, abi.encode(params)))), + (uint128, uint256, uint256) + ); } - /// @notice Add liquidity to an initialized pool - function addLiquidity(AddLiquidityParams memory params) internal returns (uint128 liquidity, BalanceDelta delta) { - // TODO: copy over addLiquidity helper here + function addLiquidityCallback(AddLiquidityParams memory params) + internal + returns (uint128 liquidity, BalanceDelta delta) + { + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(params.poolKey.toId()); + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(params.tickLower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(params.tickUpper); + liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96, params.amount0Desired, params.amount1Desired + ); + delta = poolManager.modifyPosition( + params.poolKey, + IPoolManager.ModifyPositionParams(params.tickLower, params.tickUpper, int256(int128(liquidity))), + params.hookData + ); + if ( + uint256(int256(delta.amount0())) < params.amount0Min || uint256(int256(delta.amount1())) < params.amount1Min + ) revert PriceSlippage(); } - function lockAcquired(bytes calldata rawData) external override returns (bytes memory) { - // TODO: handle mint/add/decrease liquidity here + function settleDeltas(address from, PoolKey memory poolKey, BalanceDelta delta) internal { + if (delta.amount0() > 0) { + pay(poolKey.currency0, from, address(poolManager), uint256(int256(delta.amount0()))); + poolManager.settle(poolKey.currency0); + } else if (delta.amount0() < 0) { + poolManager.take(poolKey.currency0, address(this), uint128(-delta.amount0())); + } + + if (delta.amount1() > 0) { + pay(poolKey.currency0, from, address(poolManager), uint256(int256(delta.amount1()))); + poolManager.settle(poolKey.currency1); + } else if (delta.amount1() < 0) { + poolManager.take(poolKey.currency1, address(this), uint128(-delta.amount1())); + } + } + + function lockAcquired(bytes calldata data) external override returns (bytes memory) { + CallbackData memory callbackData = abi.decode(data, (CallbackData)); + if (callbackData.callbackType == CallbackType.AddLiquidity) { + AddLiquidityParams memory params = abi.decode(callbackData.params, (AddLiquidityParams)); + (uint128 liquidity, BalanceDelta delta) = addLiquidityCallback(params); + settleDeltas(callbackData.sender, params.poolKey, delta); + return abi.encode(liquidity, delta.amount0(), delta.amount1()); + } + + // TODO: handle add/decrease liquidity here return abi.encode(0); } } diff --git a/contracts/interfaces/ILiquidityManagement.sol b/contracts/interfaces/ILiquidityManagement.sol index 5c13310e..84833cd2 100644 --- a/contracts/interfaces/ILiquidityManagement.sol +++ b/contracts/interfaces/ILiquidityManagement.sol @@ -6,41 +6,4 @@ import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; /// @title Liquidity management interface /// @notice Wrapper around pool manager callbacks -interface ILiquidityManagement is ILockCallback { - struct MintParams { - PoolKey poolKey; - int24 tickLower; - int24 tickUpper; - uint256 amount0Desired; - uint256 amount1Desired; - uint256 amount0Min; - uint256 amount1Min; - address recipient; - uint256 deadline; - bytes hookData; - } - - struct IncreaseLiquidityParams { - uint256 tokenId; - uint256 amount0Desired; - uint256 amount1Desired; - uint256 amount0Min; - uint256 amount1Min; - uint256 deadline; - } - - struct DecreaseLiquidityParams { - uint256 tokenId; - uint128 liquidity; - uint256 amount0Min; - uint256 amount1Min; - uint256 deadline; - } - - struct CollectParams { - uint256 tokenId; - address recipient; - uint128 amount0Max; - uint128 amount1Max; - } -} +interface ILiquidityManagement is ILockCallback {} diff --git a/contracts/interfaces/INonfungiblePositionManagerV4.sol b/contracts/interfaces/INonfungiblePositionManagerV4.sol index a0d12cde..d7f886d1 100644 --- a/contracts/interfaces/INonfungiblePositionManagerV4.sol +++ b/contracts/interfaces/INonfungiblePositionManagerV4.sol @@ -7,19 +7,12 @@ import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {IPeripheryPayments} from "./IPeripheryPayments.sol"; -import {ILiquidityManagement} from "./ILiquidityManagement.sol"; import {IPeripheryImmutableState} from "./IPeripheryImmutableState.sol"; /// @title Non-fungible token for positions /// @notice Wraps Uniswap V4 positions in a non-fungible token interface which allows for them to be transferred /// and authorized. -interface INonfungiblePositionManagerV4 is - ILiquidityManagement, - IPeripheryPayments, - IPeripheryImmutableState, - IERC721Metadata, - IERC721Enumerable -{ +interface INonfungiblePositionManagerV4 is IPeripheryImmutableState, IERC721Metadata, IERC721Enumerable { /// @notice Emitted when liquidity is increased for a position NFT /// @dev Also emitted when a token is minted /// @param tokenId The ID of the token for which liquidity was increased @@ -82,6 +75,19 @@ interface INonfungiblePositionManagerV4 is uint128 tokensOwed1 ); + struct MintParams { + PoolKey poolKey; + int24 tickLower; + int24 tickUpper; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + address recipient; + uint256 deadline; + bytes hookData; + } + /// @notice Creates a new position wrapped in a NFT /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized /// a method does not exist, i.e. the pool is assumed to be initialized. @@ -95,6 +101,15 @@ interface INonfungiblePositionManagerV4 is payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); + struct IncreaseLiquidityParams { + uint256 tokenId; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + } + /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender` /// @param params tokenId The ID of the token for which liquidity is being increased, /// amount0Desired The desired amount of token0 to be spent, @@ -110,6 +125,14 @@ interface INonfungiblePositionManagerV4 is payable returns (uint128 liquidity, uint256 amount0, uint256 amount1); + struct DecreaseLiquidityParams { + uint256 tokenId; + uint128 liquidity; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + } + /// @notice Decreases the amount of liquidity in a position and accounts it to the position /// @param params tokenId The ID of the token for which liquidity is being decreased, /// amount The amount by which liquidity will be decreased, @@ -123,6 +146,13 @@ interface INonfungiblePositionManagerV4 is payable returns (uint256 amount0, uint256 amount1); + struct CollectParams { + uint256 tokenId; + address recipient; + uint128 amount0Max; + uint128 amount1Max; + } + /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient /// @param params tokenId The ID of the NFT for which tokens are being collected, /// recipient The account that should receive the tokens,