Smart Contract

SwapPair

A.054c33eaf904f2ec.SwapPair

Deployed

2h ago
Feb 28, 2026, 05:07:51 PM UTC

Dependents

0 imports
1/**
2
3# SwapPair
4
5# Author: Increment Labs
6
7*/
8import FungibleToken from 0xf233dcee88fe0abe
9import SwapInterfaces from 0xb78ef7afa52ff906
10import SwapConfig from 0xb78ef7afa52ff906
11import SwapError from 0xb78ef7afa52ff906
12import SwapFactory from 0xb063c16cac85dbd1
13
14access(all) contract SwapPair: FungibleToken {
15    /// Total supply of pair lpTokens in existence
16    access(all) var totalSupply: UFix64
17    
18    /// Two vaults for the trading pair.
19    access(self) let token0Vault: @{FungibleToken.Vault}
20    access(self) let token1Vault: @{FungibleToken.Vault}
21    access(all) let token0VaultType: Type
22    access(all) let token1VaultType: Type
23    access(all) let token0Key: String
24    access(all) let token1Key: String
25    
26    /// TWAP: last cumulative price
27    access(all) var blockTimestampLast: UFix64
28    access(all) var price0CumulativeLastScaled: UInt256
29    access(all) var price1CumulativeLastScaled: UInt256
30
31    /// Transaction lock 
32    access(self) var lock: Bool
33    
34    /// √(reserve0 * reserve1) for volatile pool, or √√[(r0^3 * r1 + r0 * r1^3) / 2] for stable pool, as of immediately after the most recent liquidity event
35    access(all) var rootKLast: UFix64
36
37    /// Reserved parameter fields: {ParamName: Value}
38    /// Used fields:
39    ///   |__ 1. "isStableSwap" -> Bool
40    access(self) let _reservedFields: {String: AnyStruct}
41
42    /// Event that is emitted when the contract is created
43    access(all) event TokensInitialized(initialSupply: UFix64)
44    /// Event that is emitted when lp tokens are minted
45    access(all) event TokensMinted(amount: UFix64)
46    /// Event that is emitted when lp tokens are destroyed
47    access(all) event TokensBurned(amount: UFix64)
48    /// Event that is emitted when liquidity is added
49    access(all) event LiquidityAdded(amount0: UFix64, amount1: UFix64, reserve0After: UFix64, reserve1After: UFix64, amount0Type: String, amount1Type: String)
50    /// Event that is emitted when liquidity is removed
51    access(all) event LiquidityRemoved(amount0: UFix64, amount1: UFix64, reserve0After: UFix64, reserve1After: UFix64, amount0Type: String, amount1Type: String)
52    /// Event that is emitted when a swap trade happenes to this trading pair
53    access(all) event Swap(amount0In: UFix64, amount1In: UFix64, amount0Out: UFix64, amount1Out: UFix64, reserve0After: UFix64, reserve1After: UFix64, amount0Type: String, amount1Type: String)
54    /// Event that is emitted when a flashloan is originated from this SwapPair pool
55    access(all) event Flashloan(executor: Address, executorType: Type, originator: Address, requestedTokenType: String, requestedAmount: UFix64, reserve0After: UFix64, reserve1After: UFix64)
56
57    /// Lptoken Vault
58    ///
59    access(all) resource Vault: FungibleToken.Vault {
60        /// Holds the balance of a users tokens
61        access(all) var balance: UFix64
62
63        /// Initialize the balance at resource creation time
64        init(balance: UFix64) {
65            self.balance = balance
66        }
67
68        /// Called when this SwapPair's LpToken is burned via the `Burner.burn()` method
69        access(contract) fun burnCallback() {
70            if self.balance > 0.0 {
71                SwapPair.totalSupply = SwapPair.totalSupply - self.balance
72            }
73            self.balance = 0.0
74        }
75
76        /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
77        access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
78            return {self.getType(): true}
79        }
80
81        access(all) view fun isSupportedVaultType(type: Type): Bool {
82            if (type == self.getType()) { return true } else { return false }
83        }
84
85        /// Asks if the amount can be withdrawn from this vault
86        access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
87            return amount <= self.balance
88        }
89
90        /// Added simply to conform to FT-V2 interface.
91        ///
92        /// SwapPair doesn't support MetadataViews api as this is not just a normal FT, and there could be multiple types of lpTokens
93        /// for different SwapPairs, so it is stored in LpTokenCollection instead (not directly associated with a StoragePath).
94        access(all) view fun getViews(): [Type] { return [] }
95        access(all) fun resolveView(_ view: Type): AnyStruct? { return nil }
96
97        /// withdraw
98        ///
99        /// Function that takes an integer amount as an argument
100        /// and withdraws that amount from the Vault.
101        /// It creates a new temporary Vault that is used to hold
102        /// the money that is being transferred. It returns the newly
103        /// created Vault to the context that called so it can be deposited
104        /// elsewhere.
105        ///
106        access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
107            self.balance = self.balance - amount
108            return <-create Vault(balance: amount)
109        }
110
111        /// deposit
112        ///
113        /// Function that takes a Vault object as an argument and adds
114        /// its balance to the balance of the owners Vault.
115        /// It is allowed to destroy the sent Vault because the Vault
116        /// was a temporary holder of the tokens. The Vault's balance has
117        /// been consumed and therefore can be destroyed.
118        ///
119        access(all) fun deposit(from: @{FungibleToken.Vault}) {
120            let vault <- from as! @SwapPair.Vault
121            self.balance = self.balance + vault.balance
122            vault.balance = 0.0
123            destroy vault
124        }
125
126        access(all) fun createEmptyVault(): @{FungibleToken.Vault} {
127            return <-create Vault(balance: 0.0)
128        }
129    }
130
131    access(all) struct PairInfo: SwapInterfaces.PairInfo {
132        access(all) let token0Key: String
133        access(all) let token1Key: String
134        access(all) let token0Reserve: UFix64
135        access(all) let token1Reserve: UFix64
136        access(all) let pairAddr: Address
137        access(all) let lpTokenSupply: UFix64
138        access(all) let swapFeeInBps: UInt64
139        access(all) let isStableswap: Bool
140        access(all) let stableCurveP: UFix64
141
142        view init(
143            _ token0Key: String,
144            _ token1Key: String,
145            _ token0Reserve: UFix64,
146            _ token1Reserve: UFix64,
147            _ pairAddr: Address,
148            _ lpTokenSupply: UFix64,
149            _ swapFeeInBps: UInt64,
150            _ isStableswap: Bool,
151            _ stableCurveP: UFix64
152        ) {
153            self.token0Key = token0Key
154            self.token1Key = token1Key
155            self.token0Reserve = token0Reserve
156            self.token1Reserve = token1Reserve
157            self.pairAddr = pairAddr
158            self.lpTokenSupply = lpTokenSupply
159            self.swapFeeInBps = swapFeeInBps
160            self.isStableswap = isStableswap
161            self.stableCurveP = stableCurveP
162        }
163    }
164
165    /// Added simply to conform to FT-V2 interface.
166    ///
167    /// SwapPair doesn't support MetadataViews api as this is not just a normal FT, and there could be multiple types of lpTokens
168    /// for different SwapPairs, so it is stored in LpTokenCollection instead (not directly associated with a StoragePath).
169    access(all) view fun getContractViews(resourceType: Type?): [Type] { return [] }
170    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { return nil }
171
172    /// createEmptyVault
173    //
174    /// Function that creates a new Vault with a balance of zero
175    /// and returns it to the calling context. A user must call this function
176    /// and store the returned Vault in their storage in order to allow their
177    /// account to be able to receive deposits of this token type.
178    ///
179    access(all) fun createEmptyVault(vaultType: Type): @SwapPair.Vault {
180        return <-create Vault(balance: 0.0)
181    }
182
183    /// Permanently lock the first small amount lpTokens
184    access(self) fun donateInitialMinimumLpToken(donateLpBalance: UFix64) {
185        self.totalSupply = self.totalSupply + donateLpBalance
186        emit TokensMinted(amount: donateLpBalance)
187    }
188
189    /// Mint lpTokens
190    access(self) fun mintLpToken(amount: UFix64): @SwapPair.Vault {
191        self.totalSupply = self.totalSupply + amount
192        emit TokensMinted(amount: amount)
193        return <- create Vault(balance: amount)
194    } 
195
196    /// Burn lpTokens
197    access(self) fun burnLpToken(from: @SwapPair.Vault) {
198        let amount = from.balance
199        self.totalSupply = self.totalSupply - amount
200        destroy from
201        emit TokensBurned(amount: amount)
202    }
203
204    /// Add liquidity
205    ///
206    access(all) fun addLiquidity(tokenAVault: @{FungibleToken.Vault}, tokenBVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
207        pre {
208            tokenAVault.balance > 0.0 && tokenBVault.balance > 0.0:
209                SwapError.ErrorEncode(
210                    msg: "SwapPair: added zero liquidity",
211                    err: SwapError.ErrorCode.ADD_ZERO_LIQUIDITY
212                )
213            (tokenAVault.isInstance(self.token0VaultType) && tokenBVault.isInstance(self.token1VaultType)) || 
214            (tokenBVault.isInstance(self.token0VaultType) && tokenAVault.isInstance(self.token1VaultType)):
215                SwapError.ErrorEncode(
216                    msg: "SwapPair: added incompatible liquidity pair vaults",
217                    err: SwapError.ErrorCode.INVALID_PARAMETERS
218                )
219            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
220        }
221        post {
222            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
223        }
224        self.lock = true
225
226        let reserve0LastScaled = SwapConfig.UFix64ToScaledUInt256(self.token0Vault.balance)
227        let reserve1LastScaled = SwapConfig.UFix64ToScaledUInt256(self.token1Vault.balance)
228
229        /// Update twap at the first transaction in one block with the last block balance
230        self._update(reserve0Last: self.token0Vault.balance, reserve1Last: self.token1Vault.balance)
231        /// Mint fee
232        let feeOn = self._mintFee(reserve0: self.token0Vault.balance, reserve1: self.token1Vault.balance)
233
234        var liquidity = 0.0
235        var amount0Added = 0.0
236        var amount1Added = 0.0
237        if (self.totalSupply == 0.0) {
238            var donateLpBalance = 0.0
239            if self.isStableSwap() {
240                donateLpBalance = 0.0001    // 1e-4
241            } else {
242                donateLpBalance = 0.000001  // 1e-6
243            }
244            // When adding initial liquidity, the balance should not be below certain minimum amount
245            assert(tokenAVault.balance > donateLpBalance && tokenBVault.balance > donateLpBalance, message:
246                SwapError.ErrorEncode(
247                    msg: "SwapPair: less than initial minimum liquidity",
248                    err: SwapError.ErrorCode.BELOW_MINIMUM_INITIAL_LIQUIDITY
249                )
250            )
251            /// Add initial liquidity
252            if (tokenAVault.isInstance(self.token0VaultType)) {
253                amount0Added = tokenAVault.balance
254                amount1Added = tokenBVault.balance
255                self.token0Vault.deposit(from: <-tokenAVault)
256                self.token1Vault.deposit(from: <-tokenBVault)
257            } else {
258                amount0Added = tokenBVault.balance
259                amount1Added = tokenAVault.balance
260                self.token0Vault.deposit(from: <-tokenBVault)
261                self.token1Vault.deposit(from: <-tokenAVault)
262            }
263            /// Mint initial liquidity token and donate initial minimum liquidity token
264            let initialLpAmount = self._rootK(balance0: self.token0Vault.balance, balance1: self.token1Vault.balance)
265            self.donateInitialMinimumLpToken(donateLpBalance: donateLpBalance)
266
267            liquidity = initialLpAmount - donateLpBalance
268        } else {
269            var lptokenMintAmount0Scaled: UInt256 = 0
270            var lptokenMintAmount1Scaled: UInt256 = 0
271            /// Use UFIx64ToUInt256 in division & multiply to solve precision issues
272            let inAmountAScaled = SwapConfig.UFix64ToScaledUInt256(tokenAVault.balance)
273            let inAmountBScaled = SwapConfig.UFix64ToScaledUInt256(tokenBVault.balance)
274
275            let totalSupplyScaled = SwapConfig.UFix64ToScaledUInt256(self.totalSupply)
276
277            if (tokenAVault.isInstance(self.token0VaultType)) {
278                lptokenMintAmount0Scaled = inAmountAScaled * totalSupplyScaled / reserve0LastScaled
279                lptokenMintAmount1Scaled = inAmountBScaled * totalSupplyScaled / reserve1LastScaled
280                amount0Added = tokenAVault.balance
281                amount1Added = tokenBVault.balance
282                self.token0Vault.deposit(from: <-tokenAVault)
283                self.token1Vault.deposit(from: <-tokenBVault)
284            } else {
285                lptokenMintAmount0Scaled = inAmountBScaled * totalSupplyScaled / reserve0LastScaled
286                lptokenMintAmount1Scaled = inAmountAScaled * totalSupplyScaled / reserve1LastScaled
287                amount0Added = tokenBVault.balance
288                amount1Added = tokenAVault.balance
289                self.token0Vault.deposit(from: <-tokenBVault)
290                self.token1Vault.deposit(from: <-tokenAVault)
291            }
292
293            /// Note: User should add proportional liquidity as any extra is added into pool.
294            let mintLptokenAmountScaled = lptokenMintAmount0Scaled < lptokenMintAmount1Scaled ? lptokenMintAmount0Scaled : lptokenMintAmount1Scaled
295
296            /// Mint liquidity token pro rata
297            liquidity = SwapConfig.ScaledUInt256ToUFix64(mintLptokenAmountScaled)
298        }
299        /// Mint lpTokens
300        let lpTokenVault <-self.mintLpToken(amount: liquidity)
301        emit LiquidityAdded(
302            amount0: amount0Added, amount1: amount1Added,
303            reserve0After: self.token0Vault.balance, reserve1After: self.token1Vault.balance,
304            amount0Type: self.token0Key, amount1Type: self.token1Key
305        )
306
307        if feeOn {
308            self.rootKLast = self._rootK(balance0: self.token0Vault.balance, balance1: self.token1Vault.balance)
309        }
310
311        self.lock = false
312        return <-lpTokenVault
313    }
314
315    /// Remove Liquidity
316    ///
317    /// @Return: @[FungibleToken.Vault; 2]
318    ///
319    access(all) fun removeLiquidity(lpTokenVault: @{FungibleToken.Vault}) : @[{FungibleToken.Vault}] {
320        pre {
321            lpTokenVault.balance > 0.0:
322                SwapError.ErrorEncode(
323                    msg: "SwapPair: removed zero liquidity",
324                    err: SwapError.ErrorCode.INVALID_PARAMETERS
325                )
326            lpTokenVault.isInstance(Type<@SwapPair.Vault>()):
327                SwapError.ErrorEncode(
328                    msg: "SwapPair: input vault type mismatch with lpTokenVault type",
329                    err: SwapError.ErrorCode.MISMATCH_LPTOKEN_VAULT
330                )
331            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
332        }
333        post {
334            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
335        }
336        self.lock = true
337
338        let reserve0LastScaled = SwapConfig.UFix64ToScaledUInt256(self.token0Vault.balance)
339        let reserve1LastScaled = SwapConfig.UFix64ToScaledUInt256(self.token1Vault.balance)
340
341        /// Update twap
342        self._update(reserve0Last: self.token0Vault.balance, reserve1Last: self.token1Vault.balance)
343        /// Mint fee
344        let feeOn = self._mintFee(reserve0: self.token0Vault.balance, reserve1: self.token1Vault.balance)
345
346        /// Use UFIx64ToUInt256 in division & multiply to solve precision issues
347        let removeAmountScaled = SwapConfig.UFix64ToScaledUInt256(lpTokenVault.balance)
348        let totalSupplyScaled = SwapConfig.UFix64ToScaledUInt256(self.totalSupply)
349
350        let token0AmountScaled = removeAmountScaled * reserve0LastScaled / totalSupplyScaled
351        let token1AmountScaled = removeAmountScaled * reserve1LastScaled / totalSupplyScaled
352        let token0Amount = SwapConfig.ScaledUInt256ToUFix64(token0AmountScaled)
353        let token1Amount = SwapConfig.ScaledUInt256ToUFix64(token1AmountScaled)
354
355        let withdrawnToken0 <- self.token0Vault.withdraw(amount: token0Amount)
356        let withdrawnToken1 <- self.token1Vault.withdraw(amount: token1Amount)
357
358        /// Burn lpTokens
359        self.burnLpToken(from: <- (lpTokenVault as! @SwapPair.Vault))
360        emit LiquidityRemoved(
361            amount0: withdrawnToken0.balance, amount1: withdrawnToken1.balance,
362            reserve0After: self.token0Vault.balance, reserve1After: self.token1Vault.balance,
363            amount0Type: self.token0Key, amount1Type: self.token1Key
364        )
365
366        if feeOn {
367            self.rootKLast = self._rootK(balance0: self.token0Vault.balance, balance1: self.token1Vault.balance)
368        }
369
370        self.lock = false
371        return <- [<-withdrawnToken0, <-withdrawnToken1]
372    }
373
374    /// Swap
375    ///
376    access(all) fun swap(vaultIn: @{FungibleToken.Vault}, exactAmountOut: UFix64?): @{FungibleToken.Vault} {
377        pre {
378            vaultIn.balance > 0.0: SwapError.ErrorEncode(msg: "SwapPair: zero in balance", err: SwapError.ErrorCode.INVALID_PARAMETERS)
379            vaultIn.isInstance(self.token0VaultType) || vaultIn.isInstance(self.token1VaultType):
380                SwapError.ErrorEncode(
381                    msg: "SwapPair: incompatible in token vault",
382                    err: SwapError.ErrorCode.INVALID_PARAMETERS
383                )
384            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
385        }
386        post {
387            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
388        }
389        self.lock = true
390
391        self._update(reserve0Last: self.token0Vault.balance, reserve1Last: self.token1Vault.balance)
392
393        let amountIn = vaultIn.balance
394        var amountOut = 0.0
395        /// Calculate the swap result
396        if (vaultIn.isInstance(self.token0VaultType)) {
397            if self.isStableSwap() {
398                amountOut = SwapConfig.getAmountOutStable(amountIn: amountIn, reserveIn: self.token0Vault.balance, reserveOut: self.token1Vault.balance, p: self.getStableCurveP(), swapFeeRateBps: self.getSwapFeeBps())
399            } else {
400                amountOut = SwapConfig.getAmountOutVolatile(amountIn: amountIn, reserveIn: self.token0Vault.balance, reserveOut: self.token1Vault.balance, swapFeeRateBps: self.getSwapFeeBps())
401            }
402        } else {
403            if self.isStableSwap() {
404                amountOut = SwapConfig.getAmountOutStable(amountIn: amountIn, reserveIn: self.token1Vault.balance, reserveOut: self.token0Vault.balance, p: 1.0/self.getStableCurveP(), swapFeeRateBps: self.getSwapFeeBps())
405            } else {
406                amountOut = SwapConfig.getAmountOutVolatile(amountIn: amountIn, reserveIn: self.token1Vault.balance, reserveOut: self.token0Vault.balance, swapFeeRateBps: self.getSwapFeeBps())
407            }
408        }
409        /// Check and swap exact output amount if specified in argument
410        if exactAmountOut != nil {
411            assert(amountOut >= exactAmountOut!, message:
412                SwapError.ErrorEncode(
413                    msg: "SwapPair: INSUFFICIENT_OUTPUT_AMOUNT",
414                    err: SwapError.ErrorCode.INSUFFICIENT_OUTPUT_AMOUNT
415                )
416            )
417            amountOut = exactAmountOut!
418        }
419
420        if (vaultIn.isInstance(self.token0VaultType)) {
421            self.token0Vault.deposit(from: <-vaultIn)
422            emit Swap(
423                amount0In: amountIn, amount1In: 0.0,
424                amount0Out: 0.0, amount1Out: amountOut,
425                reserve0After: self.token0Vault.balance, reserve1After: self.token1Vault.balance - amountOut,
426                amount0Type: self.token0Key, amount1Type: self.token1Key
427            )
428            self.lock = false
429            return <- self.token1Vault.withdraw(amount: amountOut)
430        } else {
431            self.token1Vault.deposit(from: <-vaultIn)
432            emit Swap(
433                amount0In: 0.0, amount1In: amountIn,
434                amount0Out: amountOut, amount1Out: 0.0,
435                reserve0After: self.token0Vault.balance - amountOut, reserve1After: self.token1Vault.balance,
436                amount0Type: self.token0Key, amount1Type: self.token1Key
437            )
438            self.lock = false
439            return <- self.token0Vault.withdraw(amount: amountOut)
440        }
441    }
442
443    /// An executor contract can request to use the whole liquidity of current SwapPair and perform custom operations (like arbitrage, liquidation, et al.), as long as:
444    ///   1. executor implements FlashLoanExecutor resource interface and sets up corresponding resource to receive & process requested tokens, and
445    ///   2. executor repays back requested amount + fees (dominated by 'flashloanRateBps x amount'), and
446    /// all in one atomic function call.
447    /// @params: User-definited extra data passed to executor for further auth/check/decode
448    ///
449    access(all) fun flashloan(executor: &{SwapInterfaces.FlashLoanExecutor}, requestedTokenVaultType: Type, requestedAmount: UFix64, params: {String: AnyStruct}) {
450        pre {
451            requestedTokenVaultType == self.token0VaultType || requestedTokenVaultType == self.token1VaultType:
452                SwapError.ErrorEncode(
453                    msg: "SwapPair: flashloan invalid requested token type",
454                    err: SwapError.ErrorCode.INVALID_PARAMETERS
455                )
456            (requestedTokenVaultType == self.token0VaultType && requestedAmount > 0.0 && requestedAmount < self.token0Vault.balance) ||
457            (requestedTokenVaultType == self.token1VaultType && requestedAmount > 0.0 && requestedAmount < self.token1Vault.balance) :
458                SwapError.ErrorEncode(
459                    msg: "SwapPair: flashloan invalid requested amount",
460                    err: SwapError.ErrorCode.INVALID_PARAMETERS
461                )
462            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
463        }
464        post {
465            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
466        }
467        self.lock = true
468
469        self._update(reserve0Last: self.token0Vault.balance, reserve1Last: self.token1Vault.balance)
470
471        var tokenOut: @{FungibleToken.Vault}? <- nil
472        if (requestedTokenVaultType == self.token0VaultType) {
473            tokenOut <-! self.token0Vault.withdraw(amount: requestedAmount)
474        } else {
475            tokenOut <-! self.token1Vault.withdraw(amount: requestedAmount)
476        }
477        // Send loans and invoke custom executor
478        let tokenIn <- executor.executeAndRepay(loanedToken: <- tokenOut!, params: params)
479        assert(tokenIn.isInstance(requestedTokenVaultType), message:
480            SwapError.ErrorEncode(
481                msg: "SwapPair: flashloan repaid incompatible token",
482                err: SwapError.ErrorCode.INVALID_PARAMETERS
483            )
484        )
485        assert(tokenIn.balance >= requestedAmount * (1.0 + UFix64(SwapFactory.getFlashloanRateBps()) / 10000.0), message:
486            SwapError.ErrorEncode(
487                msg: "SwapPair: flashloan insufficient repayment",
488                err: SwapError.ErrorCode.INVALID_PARAMETERS
489            )
490        )
491        if (requestedTokenVaultType == self.token0VaultType) {
492            self.token0Vault.deposit(from: <- tokenIn)
493        } else {
494            self.token1Vault.deposit(from: <- tokenIn)
495        }
496
497        emit Flashloan(
498            executor: executor.owner!.address,
499            executorType: executor.getType(),
500            originator: self.account.address,
501            requestedTokenType: requestedTokenVaultType == self.token0VaultType ? self.token0Key : self.token1Key,
502            requestedAmount: requestedAmount,
503            reserve0After: self.token0Vault.balance,
504            reserve1After: self.token1Vault.balance
505        )
506
507        self.lock = false
508    }
509
510    /// Update cumulative price on the first call per block
511    access(self) fun _update(reserve0Last: UFix64, reserve1Last: UFix64) {
512        let blockTimestamp = getCurrentBlock().timestamp
513        let timeElapsed = blockTimestamp - self.blockTimestampLast
514        if (timeElapsed > 0.0 && reserve0Last != 0.0 && reserve1Last != 0.0) {
515            let timeElapsedScaled = SwapConfig.UFix64ToScaledUInt256(timeElapsed)
516            let stableswap_p = self.getStableCurveP()
517            let price0 = self.isStableSwap() ?
518                SwapConfig.quoteStable(amountA: 1.0, reserveA: reserve0Last, reserveB: reserve1Last, p: stableswap_p) :
519                SwapConfig.quote(amountA: 1.0, reserveA: reserve0Last, reserveB: reserve1Last)
520            var price1 = 0.0
521            if (price0 == 0.0) {
522                price1 = self.isStableSwap() ?
523                    SwapConfig.quoteStable(amountA: 1.0, reserveA: reserve1Last, reserveB: reserve0Last, p: 1.0 / stableswap_p) :
524                    SwapConfig.quote(amountA: 1.0, reserveA: reserve1Last, reserveB: reserve0Last)
525            } else {
526                price1 = 1.0 / price0
527            }
528
529            self.price0CumulativeLastScaled = SwapConfig.overflowAddUInt256(
530                self.price0CumulativeLastScaled,
531                SwapConfig.UFix64ToScaledUInt256(price0) * timeElapsedScaled / SwapConfig.scaleFactor
532            )
533            self.price1CumulativeLastScaled = SwapConfig.overflowAddUInt256(
534                self.price1CumulativeLastScaled,
535                SwapConfig.UFix64ToScaledUInt256(price1) * timeElapsedScaled / SwapConfig.scaleFactor
536            )   
537        }
538        self.blockTimestampLast = blockTimestamp
539    }
540    
541    /// If feeTo is set, mint 1/6th (the default cut) of the growth in sqrt(k) which is only generated by trading behavior.
542    /// Instead of collecting protocol fees at the time of each trade, accumulated fees are collected only
543    /// when liquidity is deposited or withdrawn.
544    ///
545    access(self) fun _mintFee(reserve0: UFix64, reserve1: UFix64): Bool {
546        let rootKLast = self.rootKLast
547        if SwapFactory.feeTo == nil {
548            if rootKLast > 0.0 {
549                self.rootKLast = 0.0
550            }
551            return false
552        }
553        if rootKLast > 0.0 {
554            let rootK = self._rootK(balance0: reserve0, balance1: reserve1)
555            if rootK > rootKLast {
556                let numerator = self.totalSupply * (rootK - rootKLast)
557                let denominator = (1.0 / SwapFactory.getProtocolFeeCut() - 1.0) * rootK + rootKLast
558                let liquidity = numerator / denominator
559                if liquidity > 0.0 {
560                    let lpTokenVault <-self.mintLpToken(amount: liquidity)
561                    let lpTokenCollectionCap = getAccount(SwapFactory.feeTo!).capabilities.get<&{SwapInterfaces.LpTokenCollectionPublic}>(SwapConfig.LpTokenCollectionPublicPath)
562                    assert(lpTokenCollectionCap.check(), message:
563                        SwapError.ErrorEncode(
564                            msg: "SwapPair: Cannot borrow reference to LpTokenCollection resource in feeTo account",
565                            err: SwapError.ErrorCode.LOST_PUBLIC_CAPABILITY
566                        )
567                    )
568                    lpTokenCollectionCap.borrow()!.deposit(pairAddr: self.account.address, lpTokenVault: <-lpTokenVault)
569                }
570            }
571        }
572        return true
573    }
574
575    access(all) view fun _rootK(balance0: UFix64, balance1: UFix64): UFix64 {
576        let e18: UInt256 = SwapConfig.scaleFactor
577        let balance0Scaled = SwapConfig.UFix64ToScaledUInt256(balance0)
578        let balance1Scaled = SwapConfig.UFix64ToScaledUInt256(balance1)
579        if self.isStableSwap() {
580            let _p_scaled: UInt256 = SwapConfig.UFix64ToScaledUInt256(self.getStableCurveP())
581            let _k_scaled: UInt256 = SwapConfig.k_stable_p(balance0Scaled, balance1Scaled, _p_scaled)
582            return SwapConfig.ScaledUInt256ToUFix64(SwapConfig.sqrt(SwapConfig.sqrt(_k_scaled / 2)))
583        } else {
584            return SwapConfig.ScaledUInt256ToUFix64(SwapConfig.sqrt(balance0Scaled * balance1Scaled / e18))
585        }
586    }
587
588    access(all) view fun isStableSwap(): Bool {
589        return (self._reservedFields["isStableSwap"] as! Bool?) ?? false
590    }
591
592    access(all) view fun getSwapFeeBps(): UInt64 {
593        return SwapFactory.getSwapFeeRateBps(stableMode: self.isStableSwap())
594    }
595
596    access(all) view fun getStableCurveP(): UFix64 {
597        return 1.0
598    }
599
600    /// Public interfaces
601    ///
602    access(all) resource PairPublic: SwapInterfaces.PairPublic {
603        access(all) fun swap(vaultIn: @{FungibleToken.Vault}, exactAmountOut: UFix64?): @{FungibleToken.Vault} {
604            return <- SwapPair.swap(vaultIn: <-vaultIn, exactAmountOut: exactAmountOut)
605        }
606
607        access(all) fun flashloan(executor: &{SwapInterfaces.FlashLoanExecutor}, requestedTokenVaultType: Type, requestedAmount: UFix64, params: {String: AnyStruct}) {
608            SwapPair.flashloan(executor: executor, requestedTokenVaultType: requestedTokenVaultType, requestedAmount: requestedAmount, params: params)
609        }
610
611        access(all) fun removeLiquidity(lpTokenVault: @{FungibleToken.Vault}) : @[{FungibleToken.Vault}] {
612            return <- SwapPair.removeLiquidity(lpTokenVault: <- lpTokenVault)
613        }
614
615        access(all) fun addLiquidity(tokenAVault: @{FungibleToken.Vault}, tokenBVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
616            return <- SwapPair.addLiquidity(tokenAVault: <- tokenAVault, tokenBVault: <- tokenBVault)
617        }
618
619        access(all) view fun getAmountIn(amountOut: UFix64, tokenOutKey: String): UFix64 {
620            if SwapPair.isStableSwap() {
621                if tokenOutKey == SwapPair.token1Key {
622                    return SwapConfig.getAmountInStable(amountOut: amountOut, reserveIn: SwapPair.token0Vault.balance, reserveOut: SwapPair.token1Vault.balance, p: SwapPair.getStableCurveP(), swapFeeRateBps: SwapPair.getSwapFeeBps())
623                } else {
624                    return SwapConfig.getAmountInStable(amountOut: amountOut, reserveIn: SwapPair.token1Vault.balance, reserveOut: SwapPair.token0Vault.balance, p: 1.0/SwapPair.getStableCurveP(), swapFeeRateBps: SwapPair.getSwapFeeBps())
625                }
626            } else {
627                if tokenOutKey == SwapPair.token1Key {
628                    return SwapConfig.getAmountInVolatile(amountOut: amountOut, reserveIn: SwapPair.token0Vault.balance, reserveOut: SwapPair.token1Vault.balance, swapFeeRateBps: SwapPair.getSwapFeeBps())
629                } else {
630                    return SwapConfig.getAmountInVolatile(amountOut: amountOut, reserveIn: SwapPair.token1Vault.balance, reserveOut: SwapPair.token0Vault.balance, swapFeeRateBps: SwapPair.getSwapFeeBps())
631                }
632            }
633        }
634        access(all) view fun getAmountOut(amountIn: UFix64, tokenInKey: String): UFix64 {
635            if SwapPair.isStableSwap() {
636                if tokenInKey == SwapPair.token0Key {
637                    return SwapConfig.getAmountOutStable(amountIn: amountIn, reserveIn: SwapPair.token0Vault.balance, reserveOut: SwapPair.token1Vault.balance, p: SwapPair.getStableCurveP(), swapFeeRateBps: SwapPair.getSwapFeeBps())
638                } else {
639                    return SwapConfig.getAmountOutStable(amountIn: amountIn, reserveIn: SwapPair.token1Vault.balance, reserveOut: SwapPair.token0Vault.balance, p: 1.0/SwapPair.getStableCurveP(), swapFeeRateBps: SwapPair.getSwapFeeBps())
640                }
641            } else {
642                if tokenInKey == SwapPair.token0Key {
643                    return SwapConfig.getAmountOutVolatile(amountIn: amountIn, reserveIn: SwapPair.token0Vault.balance, reserveOut: SwapPair.token1Vault.balance, swapFeeRateBps: SwapPair.getSwapFeeBps())
644                } else {
645                    return SwapConfig.getAmountOutVolatile(amountIn: amountIn, reserveIn: SwapPair.token1Vault.balance, reserveOut: SwapPair.token0Vault.balance, swapFeeRateBps: SwapPair.getSwapFeeBps())
646                }
647            }
648        }
649        access(all) view fun getPrice0CumulativeLastScaled(): UInt256 {
650            return SwapPair.price0CumulativeLastScaled
651        }
652        access(all) view fun getPrice1CumulativeLastScaled(): UInt256 {
653            return SwapPair.price1CumulativeLastScaled
654        }
655        access(all) view fun getBlockTimestampLast(): UFix64 {
656            return SwapPair.blockTimestampLast
657        }
658
659        access(all) view fun getPairInfo(): [AnyStruct] {
660            return [
661                SwapPair.token0Key,            // 0
662                SwapPair.token1Key,
663                SwapPair.token0Vault.balance,
664                SwapPair.token1Vault.balance,
665                SwapPair.account.address,
666                SwapPair.totalSupply,          // 5
667                SwapPair.getSwapFeeBps(),
668                SwapPair.isStableSwap(),
669                SwapPair.getStableCurveP()
670            ]
671        }
672
673        access(all) view fun getPairInfoStruct(): PairInfo {
674            return PairInfo(
675                SwapPair.token0Key,
676                SwapPair.token1Key,
677                SwapPair.token0Vault.balance,
678                SwapPair.token1Vault.balance,
679                SwapPair.account.address,
680                SwapPair.totalSupply,
681                SwapPair.getSwapFeeBps(),
682                SwapPair.isStableSwap(),
683                SwapPair.getStableCurveP()
684            )
685        }
686
687        access(all) view fun getLpTokenVaultType(): Type {
688            return Type<@SwapPair.Vault>()
689        }
690
691        access(all) view fun isStableSwap(): Bool {
692            return SwapPair.isStableSwap()
693        }
694
695        access(all) view fun getStableCurveP(): UFix64 {
696            return SwapPair.getStableCurveP()
697        }
698    }
699
700    init(token0Vault: @{FungibleToken.Vault}, token1Vault: @{FungibleToken.Vault}, stableMode: Bool) {
701        self.totalSupply = 0.0
702
703        self.token0VaultType = token0Vault.getType()
704        self.token1VaultType = token1Vault.getType()
705        self.token0Vault <- token0Vault
706        self.token1Vault <- token1Vault
707
708        self.lock = false
709
710        self.blockTimestampLast = getCurrentBlock().timestamp
711        self.price0CumulativeLastScaled = 0
712        self.price1CumulativeLastScaled = 0
713
714        self.rootKLast = 0.0
715        self._reservedFields = {}
716        self._reservedFields["isStableSwap"] = stableMode
717
718        self.token0Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token0VaultType.identifier)
719        self.token1Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token1VaultType.identifier)
720
721        /// Setup PairPublic interface capability
722        destroy <-self.account.storage.load<@AnyResource>(from: /storage/pair_public)
723        self.account.storage.save(<-create PairPublic(), to: /storage/pair_public)
724        let pairPublicCap = self.account.capabilities.storage.issue<&{SwapInterfaces.PairPublic}>(/storage/pair_public)
725        self.account.capabilities.publish(pairPublicCap, at: SwapConfig.PairPublicPath)
726
727        emit TokensInitialized(initialSupply: self.totalSupply)
728    }
729}
730