-
Notifications
You must be signed in to change notification settings - Fork 504
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
posm slippage #225
Changes from 8 commits
b0e5da5
4ba83c0
f7bc85c
305d2e8
f7f4908
64b1653
e353f41
471386f
4e88711
7c2d90b
a415237
3b0bd3d
5838141
946c9af
da52741
8a3cfdd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
47248 | ||
47639 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
47066 | ||
47456 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
130117 | ||
130604 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
123039 | ||
123525 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
151223 | ||
151829 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
142375 | ||
142981 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
151223 | ||
151829 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
116766 | ||
117372 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
109375 | ||
109860 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
135156 | ||
136042 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
127895 | ||
128781 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
129482 | ||
130088 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
152736 | ||
154000 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
134536 | ||
135800 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
135432 | ||
136451 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
171588 | ||
172607 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
372837 | ||
373981 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
337537 | ||
338681 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
346399 | ||
347557 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
315519 | ||
316663 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
316161 | ||
317305 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
241743 | ||
242887 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
371451 | ||
372610 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
321537 | ||
322681 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
417173 | ||
418329 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,31 +100,54 @@ contract PositionManager is | |
/// @param params is an encoding of uint256 tokenId, PositionConfig memory config, uint256 liquidity, bytes hookData | ||
/// @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, bytes memory hookData) = | ||
abi.decode(params, (uint256, PositionConfig, uint256, bytes)); | ||
( | ||
uint256 tokenId, | ||
PositionConfig memory config, | ||
uint256 liquidity, | ||
uint128 amount0Max, | ||
uint128 amount1Max, | ||
bytes memory hookData | ||
) = abi.decode(params, (uint256, PositionConfig, uint256, uint128, uint128, bytes)); | ||
|
||
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(); | ||
} | ||
|
||
/// @param params is an encoding of uint256 tokenId, PositionConfig memory config, uint256 liquidity, bytes hookData | ||
/// @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, bytes memory hookData) = | ||
abi.decode(params, (uint256, PositionConfig, uint256, bytes)); | ||
( | ||
uint256 tokenId, | ||
PositionConfig memory config, | ||
uint256 liquidity, | ||
uint128 amount0Min, | ||
uint128 amount1Min, | ||
bytes memory hookData | ||
) = abi.decode(params, (uint256, PositionConfig, uint256, uint128, uint128, bytes)); | ||
|
||
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(); | ||
} | ||
} | ||
|
||
/// @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, address owner, bytes memory hookData) = | ||
abi.decode(params, (PositionConfig, uint256, address, bytes)); | ||
( | ||
PositionConfig memory config, | ||
uint256 liquidity, | ||
uint128 amount0Max, | ||
uint128 amount1Max, | ||
address owner, | ||
bytes memory hookData | ||
) = abi.decode(params, (PositionConfig, uint256, uint128, uint128, address, bytes)); | ||
|
||
// mint receipt token | ||
uint256 tokenId; | ||
|
@@ -136,7 +159,8 @@ 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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this slippage check different from _increase? shouldn't mint and increase be the same? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. _mint is guaranteed to be negative delta so the conditional is cleaner _increase can be positive (spill over fee revenue to be collected), where a slippage check is irrelevant |
||
positionConfigs[tokenId] = config.toId(); | ||
} | ||
|
||
|
@@ -168,8 +192,8 @@ contract PositionManager is | |
/// @param params is an encoding of uint256 tokenId, PositionConfig memory config, bytes hookData | ||
/// @dev this is overloaded with ERC721Permit._burn | ||
function _burn(bytes memory params) internal { | ||
(uint256 tokenId, PositionConfig memory config, bytes memory hookData) = | ||
abi.decode(params, (uint256, PositionConfig, bytes)); | ||
(uint256 tokenId, PositionConfig memory config, uint128 amount0Min, uint128 amount1Min, bytes memory hookData) = | ||
abi.decode(params, (uint256, PositionConfig, uint128, uint128, bytes)); | ||
|
||
if (!_isApprovedOrOwner(_msgSender(), tokenId)) revert NotApproved(_msgSender()); | ||
if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId); | ||
|
@@ -179,6 +203,9 @@ 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) { | ||
saucepoint marked this conversation as resolved.
Show resolved
Hide resolved
|
||
revert SlippageExceeded(); | ||
} | ||
} | ||
|
||
delete positionConfigs[tokenId]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,7 +114,9 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { | |
|
||
// TODO: Can we make this easier to re-invest fees, so that you don't need to know the exact collect amount? | ||
Plan memory planner = Planner.init(); | ||
planner.add(Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, ZERO_BYTES)); | ||
planner.add( | ||
Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, 0 wei, 0 wei, ZERO_BYTES) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how come this is 0 and not MAX_SLIPPAGE_INCREASE? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we're ~perfectly autocompounding fees in this test so a strict slippage (no tokens paid by the caller) is an additional assurance |
||
); | ||
bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); | ||
vm.startPrank(alice); | ||
lpm.modifyLiquidities(calls, _deadline); | ||
|
@@ -369,11 +371,84 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { | |
} | ||
} | ||
|
||
function test_increaseLiquidity_slippage_revertAmount0() public { | ||
// increasing liquidity with strict slippage parameters (amount0) will revert | ||
uint256 tokenId = lpm.nextTokenId(); | ||
mint(config, 100e18, address(this), ZERO_BYTES); | ||
|
||
// revert since amount0Max is too low | ||
bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, 1 wei, type(uint128).max, ZERO_BYTES); | ||
vm.expectRevert(IPositionManager.SlippageExceeded.selector); | ||
lpm.modifyLiquidities(calls, _deadline); | ||
} | ||
|
||
function test_increaseLiquidity_slippage_revertAmount1() public { | ||
// increasing liquidity with strict slippage parameters (amount1) will revert | ||
uint256 tokenId = lpm.nextTokenId(); | ||
mint(config, 100e18, address(this), ZERO_BYTES); | ||
|
||
// revert since amount1Max is too low | ||
bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, type(uint128).max, 1 wei, ZERO_BYTES); | ||
vm.expectRevert(IPositionManager.SlippageExceeded.selector); | ||
lpm.modifyLiquidities(calls, _deadline); | ||
} | ||
|
||
function test_increaseLiquidity_slippage_exactDoesNotRevert() public { | ||
// increasing liquidity with perfect slippage parameters does not revert | ||
uint256 tokenId = lpm.nextTokenId(); | ||
mint(config, 100e18, address(this), ZERO_BYTES); | ||
|
||
uint128 newLiquidity = 10e18; | ||
(uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( | ||
SQRT_PRICE_1_1, | ||
TickMath.getSqrtPriceAtTick(config.tickLower), | ||
TickMath.getSqrtPriceAtTick(config.tickUpper), | ||
newLiquidity | ||
); | ||
assertEq(amount0, amount1); // symmetric liquidity addition | ||
uint128 slippage = uint128(amount0) + 1; | ||
|
||
bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, slippage, slippage, ZERO_BYTES); | ||
lpm.modifyLiquidities(calls, _deadline); | ||
BalanceDelta delta = getLastDelta(); | ||
|
||
// confirm that delta == slippage tolerance | ||
assertEq(-delta.amount0(), int128(slippage)); | ||
assertEq(-delta.amount1(), int128(slippage)); | ||
} | ||
|
||
/// price movement from swaps will cause slippage reverts | ||
function test_increaseLiquidity_slippage_revert_swap() public { | ||
// increasing liquidity with perfect slippage parameters does not revert | ||
uint256 tokenId = lpm.nextTokenId(); | ||
mint(config, 100e18, address(this), ZERO_BYTES); | ||
|
||
uint128 newLiquidity = 10e18; | ||
(uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( | ||
SQRT_PRICE_1_1, | ||
TickMath.getSqrtPriceAtTick(config.tickLower), | ||
TickMath.getSqrtPriceAtTick(config.tickUpper), | ||
newLiquidity | ||
); | ||
assertEq(amount0, amount1); // symmetric liquidity addition | ||
uint128 slippage = uint128(amount0) + 1; | ||
|
||
// swap to create slippage | ||
swap(key, true, -10e18, ZERO_BYTES); | ||
|
||
bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, slippage, slippage, ZERO_BYTES); | ||
vm.expectRevert(IPositionManager.SlippageExceeded.selector); | ||
lpm.modifyLiquidities(calls, _deadline); | ||
} | ||
|
||
function test_mint_settleWithBalance() public { | ||
uint256 liquidityAlice = 3_000e18; | ||
|
||
Plan memory planner = Planner.init(); | ||
planner.add(Actions.MINT_POSITION, abi.encode(config, liquidityAlice, alice, ZERO_BYTES)); | ||
planner.add( | ||
Actions.MINT_POSITION, | ||
abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) | ||
); | ||
planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); | ||
planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); | ||
planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); | ||
|
@@ -422,7 +497,10 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { | |
|
||
// alice increases with the balance in the position manager | ||
Plan memory planner = Planner.init(); | ||
planner.add(Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityAlice, ZERO_BYTES)); | ||
planner.add( | ||
Actions.INCREASE_LIQUIDITY, | ||
abi.encode(tokenIdAlice, config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) | ||
); | ||
planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); | ||
planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); | ||
planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
helper func! same code is happening twice
_checkSlippage(amount, amountMax);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a case where liquidityDelta < 0 ? I guess a weird hook?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
liqDelta < 0
-- majority of cases, users paying tokens to increase liquidity. where we want to ensure slippage0 < liqDelta
-- cases where fee revenue spillover needs to be collectedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right i forgot about spillover case