Smart Contract
IncrementFiPoolLiquidityConnectors
A.efa9bd7d1b17f1ed.IncrementFiPoolLiquidityConnectors
1import FungibleToken from 0xf233dcee88fe0abe
2
3import SwapConnectors from 0x0bce04a00aedf132
4import DeFiActions from 0x92195d814edf9cb0
5
6import SwapRouter from 0xa6850776a94e6551
7import SwapConfig from 0xb78ef7afa52ff906
8import SwapFactory from 0xb063c16cac85dbd1
9import StableSwapFactory from 0xb063c16cac85dbd1
10import SwapInterfaces from 0xb78ef7afa52ff906
11
12/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
13/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
14/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
15///
16/// IncrementFiPoolLiquidityConnectors
17/// Connector for adding liquidity to IncrementFi pools using one token.
18///
19access(all) contract IncrementFiPoolLiquidityConnectors {
20
21 /// An implementation of DeFiActions.Swapper connector that swaps token0 to token1 and adds liquidity
22 /// to the pool using both tokens. It will then return the LP token. It is commonly called a
23 /// "zap" operation in other protocols.
24 ///
25 access(all) struct Zapper : DeFiActions.Swapper {
26
27 /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
28 /// specific Identifier to associated connectors on construction
29 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
30 /// The pools token0 type
31 access(self) let token0Type: Type
32 /// The pools token1 type
33 access(self) let token1Type: Type
34 /// The pools LP token type
35 access(self) let lpType: Type
36 /// Stable pool mode flag
37 access(self) let stableMode: Bool
38 /// The address to access pair capabilities
39 access(all) let pairAddress: Address
40
41 init(
42 token0Type: Type,
43 token1Type: Type,
44 stableMode: Bool,
45 uniqueID: DeFiActions.UniqueIdentifier?
46 ) {
47 self.token0Type = token0Type
48 self.token1Type = token1Type
49 self.stableMode = stableMode
50 self.uniqueID = uniqueID
51
52 let token0Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: token0Type.identifier)
53 let token1Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: token1Type.identifier)
54
55 self.pairAddress = (stableMode)?
56 StableSwapFactory.getPairAddress(token0Key: token0Key, token1Key: token1Key)
57 ?? panic("nonexistent stable pair \(token0Key) -> \(token1Key)")
58 :
59 SwapFactory.getPairAddress(token0Key: token0Key, token1Key: token1Key)
60 ?? panic("nonexistent pair \(token0Key) -> \(token1Key)")
61
62 let pairPublicRef = getAccount(self.pairAddress)
63 .capabilities.borrow<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath)!
64 self.lpType = pairPublicRef.getLpTokenVaultType()
65 }
66
67 /// Returns a list of ComponentInfo for each component in the stack
68 ///
69 /// @return a list of ComponentInfo for each inner DeFiActions component
70 ///
71 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
72 return DeFiActions.ComponentInfo(
73 type: self.getType(),
74 id: self.id() ?? nil,
75 innerComponents: []
76 )
77 }
78 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
79 /// a DeFiActions stack. See DeFiActions.align() for more information.
80 ///
81 /// @return a copy of the struct's UniqueIdentifier
82 ///
83 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
84 return self.uniqueID
85 }
86
87 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
88 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
89 ///
90 /// @param id: the UniqueIdentifier to set for this component
91 ///
92 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
93 self.uniqueID = id
94 }
95
96 /// The type of Vault this Swapper accepts when performing a swap
97 access(all) view fun inType(): Type {
98 return self.token0Type
99 }
100
101 /// The type of Vault this Swapper provides when performing a swap
102 /// In a zap operation, the outType is the LP token type
103 access(all) view fun outType(): Type {
104 return self.lpType
105 }
106
107 /// The estimated amount required to provide a Vault with the desired output balance
108 ///
109 /// Note: The returned quote is the best estimate for the input amount and the corresponding
110 /// output amount. The output amount may be slightly different from the desired output amount
111 /// due to the precision of the UFix64 type.
112 /// This function returns 0.0 for unachievable amounts.
113 ///
114 /// @param forDesired: the amount of the output token to receive
115 /// @param reverse: if reverse is false, will estimate the amount of token0 to provide for a desired LP amount
116 /// if reverse is true, will estimate the amount of LP tokens to provide for a desired token0 amount
117 ///
118 /// @return a DeFiActions.Quote struct containing the estimated amount required to provide a Vault with the desired output balance
119 ///
120 access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} {
121 // Handle zero amount case gracefully
122 if (forDesired == 0.0) {
123 return SwapConnectors.BasicQuote(
124 inType: reverse ? self.outType() : self.inType(),
125 outType: reverse ? self.inType() : self.outType(),
126 inAmount: 0.0,
127 outAmount: 0.0
128 )
129 }
130
131 let pairPublicRef = self.getPairPublicRef()
132 let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
133 let token0Reserve = tokenReserves[0]
134 let token1Reserve = tokenReserves[1]
135 assert(token0Reserve > 0.0 && token1Reserve > 0.0, message: "Pool must have positive reserves")
136
137 let pairInfo = pairPublicRef.getPairInfo()
138 let lpTokenSupply = pairInfo[5] as! UFix64
139 assert(lpTokenSupply > 0.0, message: "Pool must have positive LP token supply")
140
141 // The number of epochs to run the binary search for
142 // It takes ~64 iterations to exhaust UFix64 precision
143 let estimationEpochs = 64
144
145 // Use binary search to find the optimal input amount
146 // Start with reasonable bounds based on current reserves
147 var minInput = SwapConfig.ufix64NonZeroMin
148 var maxInput = 0.0
149 if (!reverse) {
150 let maxLpMintAmount = self.getMaxLpMintAmount(pairPublicRef: pairPublicRef)
151 // Unachievable
152 if forDesired > maxLpMintAmount {
153 return SwapConnectors.BasicQuote(
154 inType: self.inType(),
155 outType: self.outType(),
156 inAmount: 0.0,
157 outAmount: forDesired
158 )
159 }
160
161 // Top bound to calculate how much token0 we'd need to provide to get the desired LP amount
162 maxInput = UFix64.max
163 } else {
164 let maxToken0Returned = self.getMaxToken0Returned(pairPublicRef: pairPublicRef)
165 // Unachievable
166 if forDesired > maxToken0Returned {
167 return SwapConnectors.BasicQuote(
168 inType: self.outType(),
169 outType: self.inType(),
170 inAmount: 0.0,
171 outAmount: forDesired
172 )
173 }
174
175 // Top bound to calculate how much LP tokens we'd need to provide to get the desired token0 amount
176 maxInput = lpTokenSupply
177 }
178
179 // Binary search to find the input amount that produces the desired output
180 var bestResult = 0.0
181 var bestInput = 0.0
182 var bestDiff = 0.0
183 var epoch = 0
184 while (epoch < estimationEpochs) {
185 let midInput = minInput * 0.5 + maxInput * 0.5
186
187 // Calculate how much tokens we'd get from this input
188 let result = self.quoteOut(forProvided: midInput, reverse: reverse).outAmount
189
190 // Track the best result we've seen
191 // Note: We look for numbers that are less than the desired amount.
192 let currentDiff = result <= forDesired ? forDesired - result : UFix64.max
193 if (bestResult == 0.0 || currentDiff < bestDiff) {
194 bestDiff = currentDiff
195 bestResult = result
196 bestInput = midInput
197 }
198
199 if (result > forDesired) {
200 maxInput = midInput
201 } else if (result < forDesired) {
202 minInput = midInput
203 } else {
204 break
205 }
206
207 // Precision check, we can't be more precise than this for midInput
208 if (maxInput - minInput <= SwapConfig.ufix64NonZeroMin) {
209 break
210 }
211
212 epoch = epoch + 1
213 }
214
215 // Final validation
216 assert(bestInput > 0.0, message: "Failed to calculate valid input amount")
217 assert(bestResult > 0.0, message: "Failed to calculate valid result")
218
219 return SwapConnectors.BasicQuote(
220 inType: reverse ? self.outType() : self.inType(),
221 outType: reverse ? self.inType() : self.outType(),
222 inAmount: bestInput,
223 outAmount: bestResult
224 )
225 }
226
227 /// The estimated amount delivered out for a provided input balance
228 ///
229 /// @param forProvided: the amount of the input token to provide
230 /// @param reverse: if reverse is false, will estimate the amount of LP tokens received for a provided input balance
231 /// if reverse is true, will estimate the amount of token0 received for a provided LP token balance
232 ///
233 /// @return a DeFiActions.Quote struct containing the estimated amount delivered out for a provided input balance
234 ///
235 access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} {
236 // Handle zero amount case gracefully
237 if (forProvided == 0.0) {
238 return SwapConnectors.BasicQuote(
239 inType: reverse ? self.outType() : self.inType(),
240 outType: reverse ? self.inType() : self.outType(),
241 inAmount: 0.0,
242 outAmount: 0.0
243 )
244 }
245
246 let pairPublicRef = self.getPairPublicRef()
247 let token0Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token0Type.identifier)
248 let token1Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token1Type.identifier)
249 if (!reverse) {
250 // Calculate how much to zap from token0 to token1
251 let zappedAmount = self.calculateZappedAmount(forProvided: forProvided, pairPublicRef: pairPublicRef)
252
253 // Calculate how much we get after swapping zappedAmount of token0 to token1
254 let swappedAmount = pairPublicRef.getAmountOut(amountIn: zappedAmount, tokenInKey: token0Key)
255
256 // Calculate lp tokens we're receiving
257 let lpAmount = self.calculateLpAmount(
258 token0Amount: forProvided - zappedAmount,
259 token1Amount: swappedAmount,
260 token0Offset: zappedAmount,
261 token1Offset: swappedAmount,
262 pairPublicRef: pairPublicRef
263 )
264
265 return SwapConnectors.BasicQuote(
266 inType: self.inType(),
267 outType: self.outType(),
268 inAmount: forProvided,
269 outAmount: lpAmount
270 )
271 } else {
272 // Reverse operation: calculate how much token0Vault you get when providing LP tokens
273
274 let lpSupply = pairPublicRef.getPairInfo()[5] as! UFix64
275 // Unachievable
276 if forProvided > lpSupply {
277 return SwapConnectors.BasicQuote(
278 inType: self.inType(),
279 outType: self.outType(),
280 inAmount: forProvided,
281 outAmount: 0.0
282 )
283 }
284
285 // Calculate how much token0 and token1 you get from removing liquidity
286 let tokenAmounts = self.calculateTokenAmountsFromLp(lpAmount: forProvided, pairPublicRef: pairPublicRef)
287 let token0Amount = tokenAmounts[0]
288 let token1Amount = tokenAmounts[1]
289
290 // Calculate how much token0 you get when swapping token1 back to token0
291 let swappedToken0Amount = self.calculateSwapAmount(
292 amountIn: token1Amount,
293 token0Offset: -Fix64(token0Amount),
294 token1Offset: -Fix64(token1Amount),
295 pairPublicRef: pairPublicRef,
296 reverse: true
297 )
298
299 // Total token0 amount = direct token0 + swapped token0
300 let totalToken0Amount = token0Amount + swappedToken0Amount
301
302 return SwapConnectors.BasicQuote(
303 inType: self.outType(), // LP token type
304 outType: self.inType(), // token0 type
305 inAmount: forProvided,
306 outAmount: totalToken0Amount
307 )
308 }
309 }
310
311 /// Converts inToken to LP token
312 access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
313 let pairPublicRef = self.getPairPublicRef()
314 let zappedAmount = self.calculateZappedAmount(forProvided: inVault.balance, pairPublicRef: pairPublicRef)
315
316 // Swap
317 let swapVaultIn <- inVault.withdraw(amount: zappedAmount)
318 let token1Vault <- pairPublicRef.swap(vaultIn: <-swapVaultIn, exactAmountOut: nil)
319
320 // Add liquidity
321 let lpTokenVault <- pairPublicRef.addLiquidity(
322 tokenAVault: <- inVault,
323 tokenBVault: <- token1Vault
324 )
325
326 // Return the LP token vault
327 return <-lpTokenVault
328 }
329
330 /// Converts back LP token to inToken
331 access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
332 let pairPublicRef = self.getPairPublicRef()
333
334 // Remove liquidity
335 let tokens <- pairPublicRef.removeLiquidity(lpTokenVault: <-residual)
336 let token0Vault <- tokens[0].withdraw(amount: tokens[0].balance)
337 let token1Vault <- tokens[1].withdraw(amount: tokens[1].balance)
338 destroy tokens
339
340 // Swap token1 to token0
341 let swappedVault <- pairPublicRef.swap(vaultIn: <-token1Vault, exactAmountOut: nil)
342 token0Vault.deposit(from: <-swappedVault)
343
344 return <-token0Vault
345 }
346
347 /// Returns a reference to the pair public interface
348 access(self) view fun getPairPublicRef(): &{SwapInterfaces.PairPublic} {
349 return getAccount(self.pairAddress)
350 .capabilities.borrow<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath)!
351 }
352
353 /// Calculates the zapped amount for a given provided amount
354 /// This amount is swapped from token A to token B in order to add liquidity to the pool
355 ///
356 /// Based on https://github.com/IncrementFi/Swap/blob/main/src/scripts/query/query_zapped_amount.cdc
357 ///
358 /// @param forProvided: the total amount of the input token0
359 /// @param pairPublicRef: a reference to the pair public interface
360 ///
361 /// @return the amount of token0 to convert to token1 before adding liquidity
362 ///
363 access(self) view fun calculateZappedAmount(
364 forProvided: UFix64,
365 pairPublicRef: &{SwapInterfaces.PairPublic},
366 ): UFix64 {
367 let pairInfo = pairPublicRef.getPairInfo()
368 let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
369 var token0Reserve = tokenReserves[0]
370 var token1Reserve = tokenReserves[1]
371 assert(token0Reserve != 0.0, message: "Cannot add liquidity zapped in a new pool.")
372 var zappedAmount = 0.0
373 if !self.stableMode {
374 // Cal optimized zapped amount through dex
375 let r0Scaled = SwapConfig.UFix64ToScaledUInt256(token0Reserve)
376 let swapFeeRateBps = pairInfo[6] as! UInt64
377 let fee = 1.0 - UFix64(swapFeeRateBps)/10000.0
378 let kplus1SquareScaled = SwapConfig.UFix64ToScaledUInt256((1.0+fee)*(1.0+fee))
379 let kScaled = SwapConfig.UFix64ToScaledUInt256(fee)
380 let kplus1Scaled = SwapConfig.UFix64ToScaledUInt256(fee+1.0)
381 let token0InScaled = SwapConfig.UFix64ToScaledUInt256(forProvided)
382 let qScaled = SwapConfig.sqrt(
383 r0Scaled * r0Scaled / SwapConfig.scaleFactor * kplus1SquareScaled / SwapConfig.scaleFactor
384 + 4 * kScaled * r0Scaled / SwapConfig.scaleFactor * token0InScaled / SwapConfig.scaleFactor)
385 zappedAmount = SwapConfig.ScaledUInt256ToUFix64(
386 (qScaled - r0Scaled*kplus1Scaled/SwapConfig.scaleFactor)*SwapConfig.scaleFactor/(kScaled*2)
387 )
388 } else {
389 var desiredZappedAmount = 0.0
390 let reserve0Scaled = SwapConfig.UFix64ToScaledUInt256(token0Reserve)
391 let reserve1Scaled = SwapConfig.UFix64ToScaledUInt256(token1Reserve)
392 let forProvidedScaled = SwapConfig.UFix64ToScaledUInt256(forProvided)
393 if (token0Reserve > token1Reserve) {
394 desiredZappedAmount = SwapConfig.ScaledUInt256ToUFix64(
395 forProvidedScaled * reserve1Scaled / reserve0Scaled
396 )
397 } else {
398 desiredZappedAmount = SwapConfig.ScaledUInt256ToUFix64(
399 forProvidedScaled * reserve0Scaled / reserve1Scaled
400 )
401 }
402 let token0Key = self.token0Type.identifier
403 var desiredAmountOut = pairPublicRef.getAmountOut(amountIn: desiredZappedAmount, tokenInKey: token0Key)
404 var propAmountOut = 0.0
405 var minAmount = SwapConfig.ufix64NonZeroMin
406 var maxAmount = forProvided - SwapConfig.ufix64NonZeroMin
407 var midAmount = 0.0
408 if desiredAmountOut <= token1Reserve {
409 propAmountOut = (forProvided - desiredZappedAmount) / (token0Reserve + desiredZappedAmount) * (token1Reserve - desiredAmountOut)
410 var bias = 0.0
411 if (desiredAmountOut > propAmountOut) {
412 bias = desiredAmountOut - propAmountOut
413 } else {
414 bias = propAmountOut - desiredAmountOut
415 }
416 if (bias <= 0.0001) {
417 return desiredZappedAmount
418 } else {
419 if (desiredAmountOut > propAmountOut) {
420 maxAmount = desiredZappedAmount
421 } else {
422 minAmount = desiredZappedAmount
423 }
424 }
425 } else {
426 maxAmount = desiredZappedAmount
427 }
428 var epoch = 0
429 while (epoch < 36) {
430 midAmount = minAmount * 0.5 + maxAmount * 0.5;
431 if maxAmount - midAmount < SwapConfig.ufix64NonZeroMin {
432 break
433 }
434 let amountOut = pairPublicRef.getAmountOut(amountIn: midAmount, tokenInKey: token0Key)
435 let reserveAft0 = token0Reserve + midAmount
436 if amountOut <= token1Reserve {
437 let reserveAft1 = token1Reserve - amountOut
438 let ratioUser = (forProvided - midAmount) / amountOut
439 let ratioPool = reserveAft0 / reserveAft1
440 var ratioBias = 0.0
441 if (ratioUser >= ratioPool) {
442 if (ratioUser - ratioPool) <= SwapConfig.ufix64NonZeroMin {
443 break
444 }
445 minAmount = midAmount
446 } else {
447 if (ratioPool - ratioUser) <= SwapConfig.ufix64NonZeroMin {
448 break
449 }
450 maxAmount = midAmount
451 }
452 } else {
453 maxAmount = midAmount
454 }
455
456 epoch = epoch + 1
457 }
458 zappedAmount = midAmount
459 }
460 return zappedAmount
461 }
462
463 /// Calculates the amount of LP tokens received for a given token0Amount and token1Amount
464 ///
465 /// Based on "addLiquidity" function in https://github.com/IncrementFi/Swap/blob/main/src/contracts/SwapPair.cdc
466 ///
467 /// @param token0Amount: the amount of token0 to add to the pool
468 /// @param token1Amount: the amount of token1 to add to the pool
469 /// @param token0Offset: the offset of token0 reserves, used to simulate the impact of a swap on the reserves (added)
470 /// @param token1Offset: the offset of token1 reserves, used to simulate the impact of a swap on the reserves (subtracted)
471 /// @param pairPublicRef: a reference to the pair public interface
472 ///
473 /// @return the amount of LP tokens received
474 ///
475 access(self) view fun calculateLpAmount(
476 token0Amount: UFix64,
477 token1Amount: UFix64,
478 token0Offset: UFix64,
479 token1Offset: UFix64,
480 pairPublicRef: &{SwapInterfaces.PairPublic},
481 ): UFix64 {
482 let pairInfo = pairPublicRef.getPairInfo()
483 let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
484 var token0Reserve = tokenReserves[0]
485 var token1Reserve = tokenReserves[1]
486
487 // Note: simulate zap swap impact on reserves
488 // Zapping always swaps token0 -> token1
489 token0Reserve = token0Reserve + token0Offset
490 token1Reserve = token1Reserve - token1Offset
491
492 let reserve0LastScaled = SwapConfig.UFix64ToScaledUInt256(token0Reserve)
493 let reserve1LastScaled = SwapConfig.UFix64ToScaledUInt256(token1Reserve)
494
495 let lpTokenSupply = pairInfo[5] as! UFix64
496
497 var liquidity = 0.0
498 var amount0Added = 0.0
499 var amount1Added = 0.0
500 if (token0Reserve == 0.0 && token1Reserve == 0.0) {
501 var donateLpBalance = 0.0
502 if self.stableMode {
503 donateLpBalance = 0.0001 // 1e-4
504 } else {
505 donateLpBalance = 0.000001 // 1e-6
506 }
507 // When adding initial liquidity, the balance should not be below certain minimum amount
508 assert(token0Amount > donateLpBalance && token1Amount > donateLpBalance, message:
509 "Token0 and token1 amounts must be greater than minimum donation amount"
510 )
511 /// Calculate rootK
512 let e18: UInt256 = SwapConfig.scaleFactor
513 let balance0Scaled = SwapConfig.UFix64ToScaledUInt256(token0Amount)
514 let balance1Scaled = SwapConfig.UFix64ToScaledUInt256(token1Amount)
515 var initialLpAmount = 0.0
516 if self.stableMode {
517 let _p_scaled: UInt256 = SwapConfig.UFix64ToScaledUInt256(1.0)
518 let _k_scaled: UInt256 = SwapConfig.k_stable_p(balance0Scaled, balance1Scaled, _p_scaled)
519 initialLpAmount = SwapConfig.ScaledUInt256ToUFix64(SwapConfig.sqrt(SwapConfig.sqrt(_k_scaled / 2)))
520 } else {
521 initialLpAmount = SwapConfig.ScaledUInt256ToUFix64(SwapConfig.sqrt(balance0Scaled * balance1Scaled / e18))
522 }
523 liquidity = initialLpAmount - donateLpBalance
524 } else {
525 var lptokenMintAmount0Scaled: UInt256 = 0
526 var lptokenMintAmount1Scaled: UInt256 = 0
527
528 /// Use UFIx64ToUInt256 in division & multiply to solve precision issues
529 let inAmountAScaled = SwapConfig.UFix64ToScaledUInt256(token0Amount)
530 let inAmountBScaled = SwapConfig.UFix64ToScaledUInt256(token1Amount)
531
532 let totalSupplyScaled = SwapConfig.UFix64ToScaledUInt256(lpTokenSupply)
533
534 lptokenMintAmount0Scaled = inAmountAScaled * totalSupplyScaled / reserve0LastScaled
535 lptokenMintAmount1Scaled = inAmountBScaled * totalSupplyScaled / reserve1LastScaled
536
537 /// Note: User should add proportional liquidity as any extra is added into pool.
538 let mintLptokenAmountScaled = lptokenMintAmount0Scaled < lptokenMintAmount1Scaled ? lptokenMintAmount0Scaled : lptokenMintAmount1Scaled
539 liquidity = SwapConfig.ScaledUInt256ToUFix64(mintLptokenAmountScaled)
540 }
541 return liquidity
542 }
543
544 /// Calculates the amount of token0 and token1 you get when removing liquidity with a given LP amount
545 ///
546 /// Based on "removeLiquidity" function in https://github.com/IncrementFi/Swap/blob/main/src/contracts/SwapPair.cdc
547 ///
548 /// @param lpAmount: the amount of LP tokens to remove
549 /// @param pairPublicRef: a reference to the pair public interface
550 ///
551 /// @return an array where [0] = token0Amount, [1] = token1Amount
552 ///
553 access(self) view fun calculateTokenAmountsFromLp(
554 lpAmount: UFix64,
555 pairPublicRef: &{SwapInterfaces.PairPublic}
556 ): [UFix64; 2] {
557 let pairInfo = pairPublicRef.getPairInfo()
558 let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
559 let token0Reserve = SwapConfig.UFix64ToScaledUInt256(tokenReserves[0])
560 let token1Reserve = SwapConfig.UFix64ToScaledUInt256(tokenReserves[1])
561
562 let lpTokenSupply = pairInfo[5] as! UFix64
563
564 // Calculate proportional amounts based on LP share
565 let lpAmountScaled = SwapConfig.UFix64ToScaledUInt256(lpAmount)
566 let lpTokenSupplyScaled = SwapConfig.UFix64ToScaledUInt256(lpTokenSupply)
567 let token0Amount = SwapConfig.ScaledUInt256ToUFix64(token0Reserve * lpAmountScaled / lpTokenSupplyScaled)
568 let token1Amount = SwapConfig.ScaledUInt256ToUFix64(token1Reserve * lpAmountScaled / lpTokenSupplyScaled)
569
570 return [token0Amount, token1Amount]
571 }
572
573 /// Returns the reserves of the token0 and token1 in the pair
574 ///
575 /// @param pairPublicRef: a reference to the pair public interface
576 ///
577 /// @return an array where [0] = token0Reserve, [1] = token1Reserve
578 ///
579 access(self) view fun getTokenReserves(
580 pairPublicRef: &{SwapInterfaces.PairPublic}
581 ): [UFix64; 2] {
582 let pairInfo = pairPublicRef.getPairInfo()
583 var token0Reserve = 0.0
584 var token1Reserve = 0.0
585 let token0Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token0Type.identifier)
586 if token0Key == (pairInfo[0] as! String) {
587 token0Reserve = (pairInfo[2] as! UFix64)
588 token1Reserve = (pairInfo[3] as! UFix64)
589 } else {
590 token0Reserve = (pairInfo[3] as! UFix64)
591 token1Reserve = (pairInfo[2] as! UFix64)
592 }
593 return [token0Reserve, token1Reserve]
594 }
595
596 /// Calculates the amount of token0 received when swapping token1 to token0 with custom reserve values
597 /// If reverse is true, the amountIn is token1Amount
598 /// If reverse is false, the amountIn is token0Amount
599 ///
600 /// @param amountIn: the amount of the input token
601 /// @param token0Offset: the offset of token0 reserves, used to simulate the impact of a swap on the reserves
602 /// @param token1Offset: the offset of token1 reserves, used to simulate the impact of a swap on the reserves
603 /// @param pairPublicRef: a reference to the pair public interface
604 /// @param reverse: if reverse is true, the amountIn is token1Amount
605 /// if reverse is false, the amountIn is token0Amount
606 ///
607 /// @return the amount out of the swap operation
608 ///
609 access(self) view fun calculateSwapAmount(
610 amountIn: UFix64,
611 token0Offset: Fix64,
612 token1Offset: Fix64,
613 pairPublicRef: &{SwapInterfaces.PairPublic},
614 reverse: Bool
615 ): UFix64 {
616
617 let pairInfo = pairPublicRef.getPairInfo()
618 let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
619 var token0Reserve = tokenReserves[0]
620 var token1Reserve = tokenReserves[1]
621
622 // Note: simulate zap swap impact on reserves
623 // Handle negative offsets carefully to prevent underflow
624 let token0ReserveWithOffset = Fix64(token0Reserve) + token0Offset
625 let token1ReserveWithOffset = Fix64(token1Reserve) + token1Offset
626
627 // Insufficient liquidity
628 if token0ReserveWithOffset <= 0.0 || token1ReserveWithOffset <= 0.0 {
629 return 0.0
630 }
631
632 // Ensure reserves don't go below minimum values
633 token0Reserve = UFix64(token0ReserveWithOffset)
634 token1Reserve = UFix64(token1ReserveWithOffset)
635
636 var swappedToken0Amount = 0.0
637 if (self.stableMode) {
638 swappedToken0Amount = SwapConfig.getAmountOutStable(
639 amountIn: amountIn,
640 reserveIn: reverse ? token1Reserve : token0Reserve,
641 reserveOut: reverse ? token0Reserve : token1Reserve,
642 p: pairInfo[8] as! UFix64,
643 swapFeeRateBps: pairInfo[6] as! UInt64
644 )
645 } else {
646 swappedToken0Amount = SwapConfig.getAmountOutVolatile(
647 amountIn: amountIn,
648 reserveIn: reverse ? token1Reserve : token0Reserve,
649 reserveOut: reverse ? token0Reserve : token1Reserve,
650 swapFeeRateBps: pairInfo[6] as! UInt64
651 )
652 }
653 return swappedToken0Amount
654 }
655
656 // Returns the maximum amount of LP tokens that can be minted
657 // It's bound by the reserves of token1 that can be swapped to token0
658 access(self) fun getMaxLpMintAmount(
659 pairPublicRef: &{SwapInterfaces.PairPublic},
660 ): UFix64 {
661 let quote = self.quoteOut(forProvided: UFix64.max, reverse: false)
662 return quote.outAmount
663 }
664
665 // Returns the maximum amount of token0 that can be returned by providing all LP tokens
666 access(self) fun getMaxToken0Returned(
667 pairPublicRef: &{SwapInterfaces.PairPublic},
668 ): UFix64 {
669 let pairInfo = pairPublicRef.getPairInfo()
670 let lpTokenSupply = pairInfo[5] as! UFix64
671 let quote = self.quoteOut(forProvided: lpTokenSupply - SwapConfig.ufix64NonZeroMin, reverse: true)
672 return quote.outAmount
673 }
674 }
675
676}
677