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

posm slippage #225

Merged
merged 16 commits into from
Aug 2, 2024
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_empty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
47639
47040
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_empty_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
47456
46858
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_nonEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
130604
129816
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_burn_nonEmpty_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
123525
122737
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
151829
149962
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
142981
141114
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_collect_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
151829
149962
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decreaseLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
117372
115505
Original file line number Diff line number Diff line change
@@ -1 +1 @@
109860
108366
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_decrease_burnEmpty.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
136042
133849
Original file line number Diff line number Diff line change
@@ -1 +1 @@
128781
126588
Original file line number Diff line number Diff line change
@@ -1 +1 @@
130088
128221
Original file line number Diff line number Diff line change
@@ -1 +1 @@
154000
152100
Original file line number Diff line number Diff line change
@@ -1 +1 @@
135800
133900
Original file line number Diff line number Diff line change
@@ -1 +1 @@
136451
134581
Original file line number Diff line number Diff line change
@@ -1 +1 @@
172607
170737
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
373981
372012
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_native.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
338681
336712
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_nativeWithSweep.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
347557
345221
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickLower.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
316663
314694
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_onSameTickUpper.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
317305
315336
2 changes: 1 addition & 1 deletion .forge-snapshots/PositionManager_mint_sameRange.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
242887
240918
Original file line number Diff line number Diff line change
@@ -1 +1 @@
372610
369926
Original file line number Diff line number Diff line change
@@ -1 +1 @@
322681
320712
Original file line number Diff line number Diff line change
@@ -1 +1 @@
418329
416388
128 changes: 72 additions & 56 deletions src/PositionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {PositionConfig, PositionConfigLibrary} from "./libraries/PositionConfig.
import {BaseActionsRouter} from "./base/BaseActionsRouter.sol";
import {Actions} from "./libraries/Actions.sol";
import {CalldataDecoder} from "./libraries/CalldataDecoder.sol";
import {SlippageCheckLibrary} from "./libraries/SlippageCheck.sol";

contract PositionManager is
IPositionManager,
Expand All @@ -43,6 +44,7 @@ contract PositionManager is
using TransientStateLibrary for IPoolManager;
using SafeCast for uint256;
using CalldataDecoder for bytes;
using SlippageCheckLibrary for BalanceDelta;

/// @dev The ID of the next token that will be minted. Skips 0
uint256 public nextTokenId = 1;
Expand Down Expand Up @@ -77,24 +79,48 @@ contract PositionManager is

function _handleAction(uint256 action, bytes calldata params) internal virtual override {
if (action == Actions.INCREASE_LIQUIDITY) {
(uint256 tokenId, PositionConfig calldata config, uint256 liquidity, bytes calldata hookData) =
params.decodeModifyLiquidityParams();
_increase(tokenId, config, liquidity, hookData);
(
uint256 tokenId,
PositionConfig calldata config,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
bytes calldata hookData
) = params.decodeModifyLiquidityParams();
_increase(tokenId, config, liquidity, amount0Max, amount1Max, hookData);
} else if (action == Actions.DECREASE_LIQUIDITY) {
(uint256 tokenId, PositionConfig calldata config, uint256 liquidity, bytes calldata hookData) =
params.decodeModifyLiquidityParams();
_decrease(tokenId, config, liquidity, hookData);
(
uint256 tokenId,
PositionConfig calldata config,
uint256 liquidity,
uint128 amount0Min,
uint128 amount1Min,
bytes calldata hookData
) = params.decodeModifyLiquidityParams();
_decrease(tokenId, config, liquidity, amount0Min, amount1Min, hookData);
} else if (action == Actions.MINT_POSITION) {
(PositionConfig calldata config, uint256 liquidity, address owner, bytes calldata hookData) =
params.decodeMintParams();
_mint(config, liquidity, owner, hookData);
(
PositionConfig calldata config,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) = params.decodeMintParams();
_mint(config, liquidity, amount0Max, amount1Max, owner, hookData);
} else if (action == Actions.CLOSE_CURRENCY) {
Currency currency = params.decodeCurrency();
_close(currency);
} else if (action == Actions.BURN_POSITION) {
// Will automatically decrease liquidity to 0 if the position is not already empty.
(uint256 tokenId, PositionConfig calldata config, bytes calldata hookData) = params.decodeBurnParams();
_burn(tokenId, config, hookData);
(
uint256 tokenId,
PositionConfig calldata config,
uint128 amount0Min,
uint128 amount1Min,
bytes calldata hookData
) = params.decodeBurnParams();
_burn(tokenId, config, amount0Min, amount1Min, hookData);
} else if (action == Actions.SETTLE_WITH_BALANCE) {
Currency currency = params.decodeCurrency();
_settleWithBalance(currency);
Expand All @@ -111,55 +137,45 @@ contract PositionManager is
}

/// @dev Calling increase with 0 liquidity will credit the caller with any underlying fees of the position
function _increase(bytes memory params) internal {
(
uint256 tokenId,
PositionConfig memory config,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
bytes memory hookData
) = abi.decode(params, (uint256, PositionConfig, uint256, uint128, uint128, bytes));

function _increase(
uint256 tokenId,
PositionConfig calldata config,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
bytes calldata hookData
) internal {
if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId);
// Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager.
BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData);
if (liquidityDelta.amount0() < 0 && amount0Max < uint128(-liquidityDelta.amount0())) revert SlippageExceeded();
if (liquidityDelta.amount1() < 0 && amount1Max < uint128(-liquidityDelta.amount1())) revert SlippageExceeded();
liquidityDelta.validateMaximumIncreaseSlippage(amount0Max, amount1Max);
}

/// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position
function _decrease(bytes memory params) internal {
(
uint256 tokenId,
PositionConfig memory config,
uint256 liquidity,
uint128 amount0Min,
uint128 amount1Min,
bytes memory hookData
) = abi.decode(params, (uint256, PositionConfig, uint256, uint128, uint128, bytes));

function _decrease(
uint256 tokenId,
PositionConfig calldata config,
uint256 liquidity,
uint128 amount0Min,
uint128 amount1Min,
bytes calldata hookData
) internal {
if (!_isApprovedOrOwner(_msgSender(), tokenId)) revert NotApproved(_msgSender());
if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId);

// Note: the tokenId is used as the salt.
BalanceDelta liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData);
if (uint128(liquidityDelta.amount0()) < amount0Min || uint128(liquidityDelta.amount1()) < amount1Min) {
revert SlippageExceeded();
}
liquidityDelta.validateMinimumOut(amount0Min, amount1Min);
}

/// @param params is an encoding of PositionConfig memory config, uint256 liquidity, address recipient, bytes hookData where recipient is the receiver / owner of the ERC721
function _mint(bytes memory params) internal {
(
PositionConfig memory config,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes memory hookData
) = abi.decode(params, (PositionConfig, uint256, uint128, uint128, address, bytes));

function _mint(
PositionConfig calldata config,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) internal {
// mint receipt token
uint256 tokenId;
// tokenId is assigned to current nextTokenId before incrementing it
Expand All @@ -170,8 +186,7 @@ contract PositionManager is

// _beforeModify is not called here because the tokenId is newly minted
BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData);
if (amount0Max < uint128(-liquidityDelta.amount0())) revert SlippageExceeded();
if (amount1Max < uint128(-liquidityDelta.amount1())) revert SlippageExceeded();
liquidityDelta.validateMaximumIn(amount0Max, amount1Max);
positionConfigs[tokenId] = config.toId();
}

Expand All @@ -196,10 +211,13 @@ contract PositionManager is
}

/// @dev this is overloaded with ERC721Permit._burn
function _burn(bytes memory params) internal {
(uint256 tokenId, PositionConfig memory config, uint128 amount0Min, uint128 amount1Min, bytes memory hookData) =
abi.decode(params, (uint256, PositionConfig, uint128, uint128, bytes));

function _burn(
uint256 tokenId,
PositionConfig calldata config,
uint128 amount0Min,
uint128 amount1Min,
bytes calldata hookData
) internal {
if (!_isApprovedOrOwner(_msgSender(), tokenId)) revert NotApproved(_msgSender());
if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId);
uint256 liquidity = uint256(_getPositionLiquidity(config, tokenId));
Expand All @@ -208,9 +226,7 @@ contract PositionManager is
// Can only call modify if there is non zero liquidity.
if (liquidity > 0) {
liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData);
if (uint128(liquidityDelta.amount0()) < amount0Min || uint128(liquidityDelta.amount1()) < amount1Min) {
revert SlippageExceeded();
}
liquidityDelta.validateMinimumOut(amount0Min, amount1Min);
}

delete positionConfigs[tokenId];
Expand Down
1 change: 0 additions & 1 deletion src/interfaces/IPositionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";

interface IPositionManager {
error NotApproved(address caller);
error SlippageExceeded();
error DeadlinePassed();
error IncorrectPositionConfigForTokenId(uint256 tokenId);

Expand Down
Loading
Loading