Skip to content

Commit f5337ac

Browse files
fix(v4-sdk): use adjusted liquidity value to bound maximum inputs (#357)
Co-authored-by: dianakocsis <diana.kocsis@uniswap.org>
1 parent 9bfa2af commit f5337ac

File tree

4 files changed

+403
-35
lines changed

4 files changed

+403
-35
lines changed

sdks/v4-sdk/src/PositionManager.test.ts

Lines changed: 114 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { PoolKey } from './entities/pool'
2121
import { toAddress } from './utils/currencyMap'
2222
import { MSG_SENDER } from './actionConstants'
2323
import { V4PositionPlanner } from './utils'
24+
import JSBI from 'jsbi'
2425

2526
describe('PositionManager', () => {
2627
const currency0 = new Token(1, '0x0000000000000000000000000000000000000001', 18, 't0', 'currency0')
@@ -157,6 +158,10 @@ describe('PositionManager', () => {
157158
tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM],
158159
liquidity: 5000000,
159160
})
161+
162+
// These are the max amounts that the user can possibly add to the position
163+
const { amount0, amount1 } = position.mintAmounts
164+
160165
const { calldata, value } = V4PositionManager.addCallParameters(position, {
161166
recipient,
162167
slippageTolerance,
@@ -165,14 +170,18 @@ describe('PositionManager', () => {
165170

166171
// Rebuild the calldata with the planner for the expected mint.
167172
// Note that this test verifies that the applied logic in addCallParameters is correct but does not necessarily test the validity of the calldata itself.
168-
const { amount0: amount0Max, amount1: amount1Max } = position.mintAmountsWithSlippage(slippageTolerance)
173+
const {
174+
amount0: adjustedAmount0,
175+
amount1: adjustedAmount1,
176+
liquidity: liquidityMax,
177+
} = position.maxAmountsAndLiquidityWithSlippage(slippageTolerance)
169178
planner.addAction(Actions.MINT_POSITION, [
170179
pool_0_1.poolKey,
171180
-TICK_SPACINGS[FeeAmount.MEDIUM],
172181
TICK_SPACINGS[FeeAmount.MEDIUM],
173-
5000000,
174-
toHex(amount0Max),
175-
toHex(amount1Max),
182+
toHex(liquidityMax),
183+
toHex(amount0),
184+
toHex(amount1),
176185
recipient,
177186
EMPTY_BYTES,
178187
])
@@ -181,6 +190,11 @@ describe('PositionManager', () => {
181190

182191
expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline))
183192
expect(value).toEqual('0x00')
193+
194+
// The adjusted amounts are less than or equal to the max amounts
195+
expect(JSBI.lessThanOrEqual(adjustedAmount0, amount0)).toBe(true)
196+
expect(JSBI.lessThanOrEqual(adjustedAmount1, amount1)).toBe(true)
197+
expect(JSBI.equal(adjustedAmount0, amount0) || JSBI.equal(adjustedAmount1, amount1)).toBe(true)
184198
})
185199

186200
it('succeeds for increase', () => {
@@ -191,6 +205,9 @@ describe('PositionManager', () => {
191205
liquidity: 666,
192206
})
193207

208+
// These are the max amounts that the user can possibly add to the position
209+
const { amount0, amount1 } = position.mintAmounts
210+
194211
const { calldata, value } = V4PositionManager.addCallParameters(position, {
195212
tokenId,
196213
slippageTolerance,
@@ -199,18 +216,27 @@ describe('PositionManager', () => {
199216

200217
// Rebuild the calldata with the planner for increase
201218
const planner = new V4Planner()
202-
const { amount0: amount0Max, amount1: amount1Max } = position.mintAmountsWithSlippage(slippageTolerance)
219+
const {
220+
amount0: adjustedAmount0,
221+
amount1: adjustedAmount1,
222+
liquidity: liquidityMax,
223+
} = position.maxAmountsAndLiquidityWithSlippage(slippageTolerance)
203224
planner.addAction(Actions.INCREASE_LIQUIDITY, [
204225
tokenId.toString(),
205-
666,
206-
toHex(amount0Max),
207-
toHex(amount1Max),
226+
toHex(liquidityMax),
227+
toHex(amount0),
228+
toHex(amount1),
208229
EMPTY_BYTES,
209230
])
210231
// Expect there to be a settle pair call afterwards
211232
planner.addAction(Actions.SETTLE_PAIR, [toAddress(pool_0_1.currency0), toAddress(pool_0_1.currency1)])
212233
expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline))
213234
expect(value).toEqual('0x00')
235+
236+
// The adjusted amounts are less than or equal to the max amounts
237+
expect(JSBI.lessThanOrEqual(adjustedAmount0, amount0)).toBe(true)
238+
expect(JSBI.lessThanOrEqual(adjustedAmount1, amount1)).toBe(true)
239+
expect(JSBI.equal(adjustedAmount0, amount0) || JSBI.equal(adjustedAmount1, amount1)).toBe(true)
214240
})
215241

216242
it('succeeds when createPool is true', () => {
@@ -220,6 +246,9 @@ describe('PositionManager', () => {
220246
tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM],
221247
liquidity: 90000000000000,
222248
})
249+
250+
// These are the max amounts that the user can possibly add to the position
251+
const { amount0, amount1 } = position.mintAmounts
223252
const { calldata, value } = V4PositionManager.addCallParameters(position, {
224253
recipient,
225254
slippageTolerance,
@@ -235,21 +264,30 @@ describe('PositionManager', () => {
235264
V4PositionManager.INTERFACE.encodeFunctionData('initializePool', [pool_0_1.poolKey, SQRT_PRICE_1_1.toString()])
236265
)
237266
const planner = new V4Planner()
238-
const { amount0: amount0Max, amount1: amount1Max } = position.mintAmountsWithSlippage(slippageTolerance)
267+
const {
268+
amount0: adjustedAmount0,
269+
amount1: adjustedAmount1,
270+
liquidity: liquidityMax,
271+
} = position.maxAmountsAndLiquidityWithSlippage(slippageTolerance)
239272
// Expect position to be minted correctly
240273
planner.addAction(Actions.MINT_POSITION, [
241274
pool_0_1.poolKey,
242275
-TICK_SPACINGS[FeeAmount.MEDIUM],
243276
TICK_SPACINGS[FeeAmount.MEDIUM],
244-
90000000000000,
245-
toHex(amount0Max),
246-
toHex(amount1Max),
277+
toHex(liquidityMax),
278+
toHex(amount0),
279+
toHex(amount1),
247280
recipient,
248281
EMPTY_BYTES,
249282
])
250283
planner.addAction(Actions.SETTLE_PAIR, [toAddress(pool_0_1.currency0), toAddress(pool_0_1.currency1)])
251284
expect(calldataList[1]).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline))
252285
expect(value).toEqual('0x00')
286+
287+
// The adjusted amounts are less than or equal to the max amounts
288+
expect(JSBI.lessThanOrEqual(adjustedAmount0, amount0)).toBe(true)
289+
expect(JSBI.lessThanOrEqual(adjustedAmount1, amount1)).toBe(true)
290+
expect(JSBI.equal(adjustedAmount0, amount0) || JSBI.equal(adjustedAmount1, amount1)).toBe(true)
253291
})
254292

255293
it('succeeds when useNative is set', () => {
@@ -259,6 +297,9 @@ describe('PositionManager', () => {
259297
tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM],
260298
liquidity: 1,
261299
})
300+
301+
// These are the max amounts that the user can possibly add to the position
302+
const { amount0, amount1 } = position.mintAmounts
262303
const { calldata, value } = V4PositionManager.addCallParameters(position, {
263304
recipient,
264305
slippageTolerance,
@@ -269,24 +310,32 @@ describe('PositionManager', () => {
269310
// Rebuild the data with the planner for the expected mint. MUST sweep since we are using the native currency.
270311

271312
const planner = new V4Planner()
272-
const { amount0: amount0Max, amount1: amount1Max } = position.mintAmountsWithSlippage(slippageTolerance)
313+
const {
314+
amount0: adjustedAmount0,
315+
amount1: adjustedAmount1,
316+
liquidity: liquidityMax,
317+
} = position.maxAmountsAndLiquidityWithSlippage(slippageTolerance)
273318
// Expect position to be minted correctly
274319
planner.addAction(Actions.MINT_POSITION, [
275320
pool_1_eth.poolKey,
276321
-TICK_SPACINGS[FeeAmount.MEDIUM],
277322
TICK_SPACINGS[FeeAmount.MEDIUM],
278-
1,
279-
toHex(amount0Max),
280-
toHex(amount1Max),
323+
toHex(liquidityMax),
324+
toHex(amount0),
325+
toHex(amount1),
281326
recipient,
282327
EMPTY_BYTES,
283328
])
284329

285330
planner.addAction(Actions.SETTLE_PAIR, [toAddress(pool_1_eth.currency0), toAddress(pool_1_eth.currency1)])
286331
planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency0), MSG_SENDER])
287332
expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline))
333+
expect(value).toEqual(toHex(amount0))
288334

289-
expect(value).toEqual(toHex(amount0Max))
335+
// The adjusted amounts are less than or equal to the max amounts
336+
expect(JSBI.lessThanOrEqual(adjustedAmount0, amount0)).toBe(true)
337+
expect(JSBI.lessThanOrEqual(adjustedAmount1, amount1)).toBe(true)
338+
expect(JSBI.equal(adjustedAmount0, amount0) || JSBI.equal(adjustedAmount1, amount1)).toBe(true)
290339
})
291340

292341
it('succeeds when migrate is true', () => {
@@ -296,6 +345,9 @@ describe('PositionManager', () => {
296345
tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM],
297346
liquidity: 1,
298347
})
348+
349+
// These are the max amounts that the user can possibly add to the position
350+
const { amount0, amount1 } = position.mintAmounts
299351
const { calldata, value } = V4PositionManager.addCallParameters(position, {
300352
recipient,
301353
slippageTolerance,
@@ -305,15 +357,19 @@ describe('PositionManager', () => {
305357

306358
// Rebuild the data with the planner for the expected mint. MUST sweep since we are using the native currency.
307359
const planner = new V4Planner()
308-
const { amount0: amount0Max, amount1: amount1Max } = position.mintAmountsWithSlippage(slippageTolerance)
360+
const {
361+
amount0: adjustedAmount0,
362+
amount1: adjustedAmount1,
363+
liquidity: liquidityMax,
364+
} = position.maxAmountsAndLiquidityWithSlippage(slippageTolerance)
309365
// Expect position to be minted correctly
310366
planner.addAction(Actions.MINT_POSITION, [
311367
pool_0_1.poolKey,
312368
-TICK_SPACINGS[FeeAmount.MEDIUM],
313369
TICK_SPACINGS[FeeAmount.MEDIUM],
314-
1,
315-
toHex(amount0Max),
316-
toHex(amount1Max),
370+
toHex(liquidityMax),
371+
toHex(amount0),
372+
toHex(amount1),
317373
recipient,
318374
EMPTY_BYTES,
319375
])
@@ -325,6 +381,11 @@ describe('PositionManager', () => {
325381
expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline))
326382

327383
expect(value).toEqual('0x00')
384+
385+
// The adjusted amounts are less than or equal to the max amounts
386+
expect(JSBI.lessThanOrEqual(adjustedAmount0, amount0)).toBe(true)
387+
expect(JSBI.lessThanOrEqual(adjustedAmount1, amount1)).toBe(true)
388+
expect(JSBI.equal(adjustedAmount0, amount0) || JSBI.equal(adjustedAmount1, amount1)).toBe(true)
328389
})
329390

330391
it('succeeds when migrating to an eth position', () => {
@@ -334,6 +395,9 @@ describe('PositionManager', () => {
334395
tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM],
335396
liquidity: 1,
336397
})
398+
399+
// These are the max amounts that the user can possibly add to the position
400+
const { amount0, amount1 } = position.mintAmounts
337401
const { calldata, value } = V4PositionManager.addCallParameters(position, {
338402
recipient,
339403
slippageTolerance,
@@ -344,15 +408,19 @@ describe('PositionManager', () => {
344408

345409
// Rebuild the data with the planner for the expected mint. MUST sweep since we are using the native currency.
346410
const planner = new V4Planner()
347-
const { amount0: amount0Max, amount1: amount1Max } = position.mintAmountsWithSlippage(slippageTolerance)
411+
const {
412+
amount0: adjustedAmount0,
413+
amount1: adjustedAmount1,
414+
liquidity: liquidityMax,
415+
} = position.maxAmountsAndLiquidityWithSlippage(slippageTolerance)
348416
// Expect position to be minted correctly
349417
planner.addAction(Actions.MINT_POSITION, [
350418
pool_1_eth.poolKey,
351419
-TICK_SPACINGS[FeeAmount.MEDIUM],
352420
TICK_SPACINGS[FeeAmount.MEDIUM],
353-
1,
354-
toHex(amount0Max),
355-
toHex(amount1Max),
421+
toHex(liquidityMax),
422+
toHex(amount0),
423+
toHex(amount1),
356424
recipient,
357425
EMPTY_BYTES,
358426
])
@@ -365,6 +433,11 @@ describe('PositionManager', () => {
365433
expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline))
366434

367435
expect(value).toEqual('0x00')
436+
437+
// The adjusted amounts are less than or equal to the max amounts
438+
expect(JSBI.lessThanOrEqual(adjustedAmount0, amount0)).toBe(true)
439+
expect(JSBI.lessThanOrEqual(adjustedAmount1, amount1)).toBe(true)
440+
expect(JSBI.equal(adjustedAmount0, amount0) || JSBI.equal(adjustedAmount1, amount1)).toBe(true)
368441
})
369442

370443
it('succeeds for batchPermit', () => {
@@ -375,6 +448,9 @@ describe('PositionManager', () => {
375448
liquidity: 1,
376449
})
377450

451+
// These are the max amounts that the user can possibly add to the position
452+
const { amount0, amount1 } = position.mintAmounts
453+
378454
const batchPermit: BatchPermitOptions = {
379455
owner: mockOwner,
380456
permitBatch: {
@@ -403,21 +479,30 @@ describe('PositionManager', () => {
403479
)
404480

405481
const planner = new V4Planner()
406-
const { amount0: amount0Max, amount1: amount1Max } = position.mintAmountsWithSlippage(slippageTolerance)
482+
const {
483+
amount0: adjustedAmount0,
484+
amount1: adjustedAmount1,
485+
liquidity: liquidityMax,
486+
} = position.maxAmountsAndLiquidityWithSlippage(slippageTolerance)
407487

408488
planner.addAction(Actions.MINT_POSITION, [
409489
pool_0_1.poolKey,
410490
-TICK_SPACINGS[FeeAmount.MEDIUM],
411491
TICK_SPACINGS[FeeAmount.MEDIUM],
412-
1,
413-
toHex(amount0Max),
414-
toHex(amount1Max),
492+
toHex(liquidityMax),
493+
toHex(amount0),
494+
toHex(amount1),
415495
recipient,
416496
EMPTY_BYTES,
417497
])
418498
planner.addAction(Actions.SETTLE_PAIR, [toAddress(pool_0_1.currency0), toAddress(pool_0_1.currency1)])
419499
expect(calldataList[1]).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline))
420500
expect(value).toEqual('0x00')
501+
502+
// The adjusted amounts are less than or equal to the max amounts
503+
expect(JSBI.lessThanOrEqual(adjustedAmount0, amount0)).toBe(true)
504+
expect(JSBI.lessThanOrEqual(adjustedAmount1, amount1)).toBe(true)
505+
expect(JSBI.equal(adjustedAmount0, amount0) || JSBI.equal(adjustedAmount1, amount1)).toBe(true)
421506
})
422507
})
423508

sdks/v4-sdk/src/PositionManager.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,11 @@ export abstract class V4PositionManager {
249249
)
250250

251251
// adjust for slippage
252-
const maximumAmounts = position.mintAmountsWithSlippage(options.slippageTolerance)
253-
const amount0Max = toHex(maximumAmounts.amount0)
254-
const amount1Max = toHex(maximumAmounts.amount1)
252+
const {
253+
amount0: amount0Max,
254+
amount1: amount1Max,
255+
liquidity: liquidityMax,
256+
} = position.maxAmountsAndLiquidityWithSlippage(options.slippageTolerance)
255257

256258
// We use permit2 to approve tokens to the position manager
257259
if (options.batchPermit) {
@@ -271,15 +273,15 @@ export abstract class V4PositionManager {
271273
position.pool,
272274
position.tickLower,
273275
position.tickUpper,
274-
position.liquidity,
276+
liquidityMax,
275277
amount0Max,
276278
amount1Max,
277279
recipient,
278280
options.hookData
279281
)
280282
} else {
281283
// increase
282-
planner.addIncrease(options.tokenId, position.liquidity, amount0Max, amount1Max, options.hookData)
284+
planner.addIncrease(options.tokenId, liquidityMax, amount0Max, amount1Max, options.hookData)
283285
}
284286

285287
let value: string = toHex(0)

0 commit comments

Comments
 (0)