Skip to content

Commit

Permalink
add from amount support
Browse files Browse the repository at this point in the history
  • Loading branch information
snreynolds committed Oct 24, 2024
1 parent 0c162a0 commit 9700e0e
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 19 deletions.
87 changes: 86 additions & 1 deletion src/PositionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IPositionDescriptor} from "./interfaces/IPositionDescriptor.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";

import {IPositionDescriptor} from "./interfaces/IPositionDescriptor.sol";
import {ERC721Permit_v4} from "./base/ERC721Permit_v4.sol";
import {ReentrancyLock} from "./base/ReentrancyLock.sol";
import {IPositionManager} from "./interfaces/IPositionManager.sol";
Expand All @@ -26,6 +27,7 @@ import {CalldataDecoder} from "./libraries/CalldataDecoder.sol";
import {Permit2Forwarder} from "./base/Permit2Forwarder.sol";
import {SlippageCheck} from "./libraries/SlippageCheck.sol";
import {PositionInfo, PositionInfoLibrary} from "./libraries/PositionInfoLibrary.sol";
import {LiquidityAmounts} from "./libraries/LiquidityAmounts.sol";

// 444444444
// 444444444444 444444
Expand Down Expand Up @@ -188,6 +190,18 @@ contract PositionManager is
params.decodeModifyLiquidityParams();
_increase(tokenId, liquidity, amount0Max, amount1Max, hookData);
return;
} else if (action == Actions.INCREASE_LIQUIDITY_FROM_AMOUNTS) {
(
uint256 tokenId,
uint128 amount0,
uint128 amount1,
uint128 amount0Max,
uint128 amount1Max,
bytes calldata hookData
) = params.decodeIncreaseLiquidityFromAmountsParams();
// note the amounts are mapped inside _increaseFromAmounts
_increaseFromAmounts(tokenId, amount0, amount1, amount0Max, amount1Max, hookData);
return;
} else if (action == Actions.DECREASE_LIQUIDITY) {
(uint256 tokenId, uint256 liquidity, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) =
params.decodeModifyLiquidityParams();
Expand All @@ -206,6 +220,22 @@ contract PositionManager is
) = params.decodeMintParams();
_mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData);
return;
} else if (action == Actions.MINT_POSITION_FROM_AMOUNTS) {
(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint128 amount0,
uint128 amount1,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) = params.decodeMintFromAmountsParams();
_mintFromAmounts(
poolKey, tickLower, tickUpper, amount0, amount1, amount0Max, amount1Max, owner, hookData
);
return;
} else if (action == Actions.BURN_POSITION) {
// Will automatically decrease liquidity to 0 if the position is not already empty.
(uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) =
Expand Down Expand Up @@ -264,6 +294,37 @@ contract PositionManager is
(liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max);
}

/// @dev The amounts can be mapped to the open delta in the pool manager
function _increaseFromAmounts(
uint256 tokenId,
uint128 amount0,
uint128 amount1,
uint128 amount0Max,
uint128 amount1Max,
bytes calldata hookData
) internal onlyIfApproved(msgSender(), tokenId) {
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);

amount0 = uint128(_mapTakeAmount(uint256(amount0), poolKey.currency0));
amount1 = uint128(_mapTakeAmount(uint256(amount1), poolKey.currency1));

(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());

uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtPriceAtTick(info.tickLower()),
TickMath.getSqrtPriceAtTick(info.tickUpper()),
amount0,
amount1
);

// Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager.
(BalanceDelta liquidityDelta, BalanceDelta feesAccrued) =
_modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData);
// Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued
(liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max);
}

/// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position
function _decrease(
uint256 tokenId,
Expand Down Expand Up @@ -316,6 +377,30 @@ contract PositionManager is
liquidityDelta.validateMaxIn(amount0Max, amount1Max);
}

function _mintFromAmounts(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint128 amount0,
uint128 amount1,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) internal {
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());

uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtPriceAtTick(tickLower),
TickMath.getSqrtPriceAtTick(tickUpper),
amount0,
amount1
);

_mint(poolKey, tickLower, tickUpper, uint256(liquidity), amount0Max, amount1Max, owner, hookData);
}

/// @dev this is overloaded with ERC721Permit_v4._burn
function _burn(uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData)
internal
Expand Down
39 changes: 21 additions & 18 deletions src/libraries/Actions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,34 @@ library Actions {
uint256 constant DECREASE_LIQUIDITY = 0x01;
uint256 constant MINT_POSITION = 0x02;
uint256 constant BURN_POSITION = 0x03;
uint256 constant INCREASE_LIQUIDITY_FROM_AMOUNTS = 0x04;
uint256 constant MINT_POSITION_FROM_AMOUNTS = 0x05;

// swapping
uint256 constant SWAP_EXACT_IN_SINGLE = 0x04;
uint256 constant SWAP_EXACT_IN = 0x05;
uint256 constant SWAP_EXACT_OUT_SINGLE = 0x06;
uint256 constant SWAP_EXACT_OUT = 0x07;
uint256 constant SWAP_EXACT_IN_SINGLE = 0x06;
uint256 constant SWAP_EXACT_IN = 0x07;
uint256 constant SWAP_EXACT_OUT_SINGLE = 0x08;
uint256 constant SWAP_EXACT_OUT = 0x09;
// donate
uint256 constant DONATE = 0x08;
uint256 constant DONATE = 0x10;

// closing deltas on the pool manager
// settling
uint256 constant SETTLE = 0x09;
uint256 constant SETTLE_ALL = 0x10;
uint256 constant SETTLE_PAIR = 0x11;
uint256 constant SETTLE = 0x11;
uint256 constant SETTLE_ALL = 0x12;
uint256 constant SETTLE_PAIR = 0x13;
// taking
uint256 constant TAKE = 0x12;
uint256 constant TAKE_ALL = 0x13;
uint256 constant TAKE_PORTION = 0x14;
uint256 constant TAKE_PAIR = 0x15;
uint256 constant TAKE = 0x14;
uint256 constant TAKE_ALL = 0x15;
uint256 constant TAKE_PORTION = 0x16;
uint256 constant TAKE_PAIR = 0x17;

uint256 constant SETTLE_TAKE_PAIR = 0x16;
uint256 constant CLOSE_CURRENCY = 0x17;
uint256 constant CLEAR_OR_TAKE = 0x18;
uint256 constant SWEEP = 0x19;
uint256 constant SETTLE_TAKE_PAIR = 0x18;
uint256 constant CLOSE_CURRENCY = 0x19;
uint256 constant CLEAR_OR_TAKE = 0x20;
uint256 constant SWEEP = 0x21;

// minting/burning 6909s to close deltas
uint256 constant MINT_6909 = 0x20;
uint256 constant BURN_6909 = 0x21;
uint256 constant MINT_6909 = 0x22;
uint256 constant BURN_6909 = 0x23;
}
54 changes: 54 additions & 0 deletions src/libraries/CalldataDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,30 @@ library CalldataDecoder {
hookData = params.toBytes(4);
}

/// @dev equivalent to: abi.decode(params, (uint256, uint256, uint256, uint128, uint128, bytes)) in calldata
function decodeIncreaseLiquidityFromAmountsParams(bytes calldata params)
internal
pure
returns (
uint256 tokenId,
uint128 amount0,
uint128 amount1,
uint128 amount0Max,
uint128 amount1Max,
bytes calldata hookData
)
{
assembly ("memory-safe") {
tokenId := calldataload(params.offset)
amount0 := calldataload(add(params.offset, 0x20))
amount1 := calldataload(add(params.offset, 0x40))
amount0Max := calldataload(add(params.offset, 0x60))
amount1Max := calldataload(add(params.offset, 0x80))
}

hookData = params.toBytes(5);
}

/// @dev equivalent to: abi.decode(params, (PoolKey, int24, int24, uint256, uint128, uint128, address, bytes)) in calldata
function decodeMintParams(bytes calldata params)
internal
Expand Down Expand Up @@ -113,6 +137,36 @@ library CalldataDecoder {
hookData = params.toBytes(11);
}

/// @dev equivalent to: abi.decode(params, (PoolKey, int24, int24, uint128, uint128, uint128, uint128, address, bytes)) in calldata
function decodeMintFromAmountsParams(bytes calldata params)
internal
pure
returns (
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint128 amount0,
uint128 amount1,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
)
{
assembly ("memory-safe") {
poolKey := params.offset
tickLower := calldataload(add(params.offset, 0xa0))
tickUpper := calldataload(add(params.offset, 0xc0))
amount0 := calldataload(add(params.offset, 0xe0))
amount1 := calldataload(add(params.offset, 0x100))
amount0Max := calldataload(add(params.offset, 0x120))
amount1Max := calldataload(add(params.offset, 0x140))
owner := calldataload(add(params.offset, 0x160))
}

hookData = params.toBytes(12);
}

/// @dev equivalent to: abi.decode(params, (uint256, uint128, uint128, bytes)) in calldata
function decodeBurnParams(bytes calldata params)
internal
Expand Down
75 changes: 75 additions & 0 deletions src/libraries/LiquidityAmounts.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";

/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
/// @notice Downcasts uint256 to uint128
/// @param x The uint258 to be downcasted
/// @return y The passed value, downcasted to uint128
function toUint128(uint256 x) private pure returns (uint128 y) {
require((y = uint128(x)) == x, "liquidity overflow");
}

/// @notice Computes the amount of liquidity received for a given amount of token0 and price range
/// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount0 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0)
internal
pure
returns (uint128 liquidity)
{
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96);
return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96));
}

/// @notice Computes the amount of liquidity received for a given amount of token1 and price range
/// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount1 The amount1 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1)
internal
pure
returns (uint128 liquidity)
{
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96));
}

/// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtPriceX96 A sqrt price representing the current pool prices
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount of token0 being sent in
/// @param amount1 The amount of token1 being sent in
/// @return liquidity The maximum amount of liquidity received
function getLiquidityForAmounts(
uint160 sqrtPriceX96,
uint160 sqrtPriceAX96,
uint160 sqrtPriceBX96,
uint256 amount0,
uint256 amount1
) internal pure returns (uint128 liquidity) {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);

if (sqrtPriceX96 <= sqrtPriceAX96) {
liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0);
} else if (sqrtPriceX96 < sqrtPriceBX96) {
uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0);
uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1);

liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
} else {
liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1);
}
}
}

0 comments on commit 9700e0e

Please sign in to comment.