Smart Contract

LendingPool

A.67539e86cbe9b261.LendingPool

Deployed

1w ago
Feb 18, 2026, 04:09:08 PM UTC

Dependents

12 imports
1/**
2
3# The contract implementation of the lending pool.
4
5# Author: Increment Labs
6
7Core functionalities of the lending pool smart contract supporting cross-market supply, redeem, borrow, repay, and liquidation.
8Multiple LendingPool contracts will be deployed for each of the different pooled underlying FungibleTokens.
9
10*/
11import FungibleToken from 0xf233dcee88fe0abe
12import LendingInterfaces from 0x2df970b6cdee5735
13import LendingConfig from 0x2df970b6cdee5735
14import LendingError from 0x2df970b6cdee5735
15
16access(all) contract LendingPool {
17    /// Account address the pool is deployed to, i.e. the pool 'contract address'
18    access(all) let poolAddress: Address
19    /// Initial exchange rate (when LendingPool.totalSupply == 0) between the virtual lpToken and pool underlying token
20    access(all) let scaledInitialExchangeRate: UInt256
21    /// Block number that interest was last accrued at
22    access(all) var accrualBlockNumber: UInt256
23    /// Accumulator of the total earned interest rate since the opening of the market, scaled up by 1e18
24    access(all) var scaledBorrowIndex: UInt256
25    /// Total amount of outstanding borrows of the underlying in this market, scaled up by 1e18
26    access(all) var scaledTotalBorrows: UInt256
27    /// Total amount of reserves of the underlying held in this market, scaled up by 1e18
28    access(all) var scaledTotalReserves: UInt256
29    /// Total number of virtual lpTokens, scaled up by 1e18
30    access(all) var scaledTotalSupply: UInt256
31    /// Fraction of generated interest added to protocol reserves, scaled up by 1e18
32    /// Must be in [0.0, 1.0] x scaleFactor
33    access(all) var scaledReserveFactor: UInt256
34    /// Share of seized collateral that is added to reserves when liquidation happenes, e.g. 0.028 x 1e18.
35    /// Must be in [0.0, 1.0] x scaleFactor
36    access(all) var scaledPoolSeizeShare: UInt256
37    /// { supplierAddress => # of virtual lpToken the supplier owns, scaled up by 1e18 }
38    access(self) let accountLpTokens: {Address: UInt256}
39    /// Reserved parameter fields: {ParamName: Value}
40    /// Used fields:
41    ///   |__ 1. "flashloanRateBps" -> UInt64
42    ///   |__ 2. "isFlashloanOpen" -> Bool
43    access(self) let _reservedFields: {String: AnyStruct}
44
45    /// BorrowSnapshot
46    ///
47    /// Container for borrow balance information
48    ///
49    access(all) struct BorrowSnapshot {
50        /// Total balance (with accrued interest), after applying the most recent balance-change action
51        access(all) var scaledPrincipal: UInt256
52        /// Global borrowIndex as of the most recent balance-change action
53        access(all) var scaledInterestIndex: UInt256
54    
55        init(principal: UInt256, interestIndex: UInt256) {
56            self.scaledPrincipal = principal
57            self.scaledInterestIndex = interestIndex
58        }
59    }
60
61    // { borrowerAddress => BorrowSnapshot }
62    access(self) let accountBorrows: {Address: BorrowSnapshot}
63
64    /// Model used to calculate underlying asset's borrow interest rate
65    access(all) var interestRateModelAddress: Address?
66    access(self) var interestRateModelCap: Capability<&{LendingInterfaces.InterestRateModelPublic}>?
67    
68    /// The address of the comptroller contract
69    access(all) var comptrollerAddress: Address?
70    access(self) var comptrollerCap: Capability<&{LendingInterfaces.ComptrollerPublic}>?
71
72    /// Save underlying asset deposited into this pool
73    access(self) let underlyingVault: @{FungibleToken.Vault}
74    /// Underlying type
75    access(self) let underlyingAssetType: Type
76    
77    /// Path
78    access(all) let PoolAdminStoragePath: StoragePath
79    access(all) let UnderlyingAssetVaultStoragePath: StoragePath
80    access(all) let PoolPublicStoragePath: StoragePath
81    access(all) let PoolPublicPublicPath: PublicPath
82
83    /// Event emitted when interest is accrued
84    access(all) event AccrueInterest(_ scaledCashPrior: UInt256, _ scaledInterestAccumulated: UInt256, _ scaledBorrowIndexNew: UInt256, _ scaledTotalBorrowsNew: UInt256)
85    /// Event emitted when underlying asset is deposited into pool
86    access(all) event Supply(supplier: Address, scaledSuppliedUnderlyingAmount: UInt256, scaledMintedLpTokenAmount: UInt256)
87    /// Event emitted when virtual lpToken is burnt and redeemed for underlying asset
88    access(all) event Redeem(redeemer: Address, scaledLpTokenToRedeem: UInt256, scaledRedeemedUnderlyingAmount: UInt256)
89    /// Event emitted when user borrows underlying from the pool
90    access(all) event Borrow(borrower: Address, scaledBorrowAmount: UInt256, scaledBorrowerTotalBorrows: UInt256, scaledPoolTotalBorrows: UInt256)
91    /// Event emitted when user repays underlying to pool
92    access(all) event Repay(borrower: Address, scaledActualRepayAmount: UInt256, scaledBorrowerTotalBorrows: UInt256, scaledPoolTotalBorrows: UInt256)
93    /// Event emitted when pool reserves get added
94    access(all) event ReservesAdded(donator: Address, scaledAddedUnderlyingAmount: UInt256, scaledNewTotalReserves: UInt256)
95    /// Event emitted when pool reserves is reduced
96    access(all) event ReservesReduced(scaledReduceAmount: UInt256, scaledNewTotalReserves: UInt256)
97    /// Event emitted when liquidation happenes
98    access(all) event Liquidate(liquidator: Address, borrower: Address, scaledActualRepaidUnderlying: UInt256, collateralPoolToSeize: Address, scaledCollateralPoolLpTokenSeized: UInt256)
99    /// Event emitted when interestRateModel is changed
100    access(all) event NewInterestRateModel(_ oldInterestRateModelAddress: Address?, _ newInterestRateModelAddress: Address)
101    /// Event emitted when the reserveFactor is changed
102    access(all) event NewReserveFactor(_ oldReserveFactor: UFix64, _ newReserveFactor: UFix64)
103    /// Event emitted when the poolSeizeShare is changed
104    access(all) event NewPoolSeizeShare(_ oldPoolSeizeShare: UFix64, _ newPoolSeizeShare: UFix64)
105    /// Event emitted when the comptroller is changed
106    access(all) event NewComptroller(_ oldComptrollerAddress: Address?, _ newComptrollerAddress: Address)
107    /// Event emitted when the flashloanRateChanged is changed
108    access(all) event FlashloanRateChanged(oldRateBps: UInt64, newRateBps: UInt64)
109    /// Event emitted when the isFlashloanOpen is changed
110    access(all) event FlashloanOpen(isOpen: Bool)
111    /// Event emitted when the flashloan is executed
112    access(all) event Flashloan(executor: Address, executorType: Type, originator: Address, amount: UFix64)
113
114    // Return underlying asset's type of current pool
115    access(all) view fun getUnderlyingAssetType(): String {
116        return self.underlyingAssetType.identifier
117    }
118
119    // Gets current underlying balance of this pool, scaled up by 1e18
120    access(all) view fun getPoolCash(): UInt256 {
121        return LendingConfig.UFix64ToScaledUInt256(self.underlyingVault.balance)
122    }
123
124    /// Cal accrue interest
125    ///
126    /// @Return 0. currentBlockNumber - The block number of current calculation of interest
127    ///         1. scaledBorrowIndexNew - The new accumulator of the total earned interest rate since the opening of the market
128    ///         2. scaledTotalBorrowsNew - The new total borrows after accrue interest
129    ///         3. scaledTotalReservesNew - The new total reserves after accrue interest
130    ///
131    /// Calculates interest accrued from the last checkpointed block to the current block 
132    /// This function is a readonly function and can be called by scripts.
133    ///
134    access(all) view fun accrueInterestReadonly(): [UInt256; 4] {
135        pre {
136            self.interestRateModelCap != nil && self.interestRateModelCap!.check() == true:
137                LendingError.ErrorEncode(
138                    msg: "Cannot borrow reference to InterestRateModel in pool ".concat(LendingPool.poolAddress.toString()),
139                    err: LendingError.ErrorCode.CANNOT_ACCESS_INTEREST_RATE_MODEL_CAPABILITY
140                )
141        }
142        let currentBlockNumber = UInt256(getCurrentBlock().height)
143        let accrualBlockNumberPrior = self.accrualBlockNumber
144        
145        let scaledCashPrior = self.getPoolCash()
146        let scaledBorrowPrior = self.scaledTotalBorrows
147        let scaledReservesPrior = self.scaledTotalReserves
148        let scaledBorrowIndexPrior = self.scaledBorrowIndex
149
150        // Get scaled borrow interest rate per block
151        let scaledBorrowRatePerBlock =
152            self.interestRateModelCap!.borrow()!.getBorrowRate(cash: scaledCashPrior, borrows: scaledBorrowPrior, reserves: scaledReservesPrior)
153        let blockDelta = currentBlockNumber - accrualBlockNumberPrior
154        let scaledInterestFactor = scaledBorrowRatePerBlock * blockDelta
155        let scaleFactor = LendingConfig.scaleFactor
156        let scaledInterestAccumulated = scaledInterestFactor * scaledBorrowPrior / scaleFactor
157        let scaledTotalBorrowsNew = scaledInterestAccumulated + scaledBorrowPrior
158        let scaledTotalReservesNew = self.scaledReserveFactor * scaledInterestAccumulated / scaleFactor + scaledReservesPrior
159        let scaledBorrowIndexNew = scaledInterestFactor * scaledBorrowIndexPrior / scaleFactor + scaledBorrowIndexPrior
160
161        return [
162            currentBlockNumber,
163            scaledBorrowIndexNew,
164            scaledTotalBorrowsNew,
165            scaledTotalReservesNew
166        ]
167    }
168
169    /// Accrue Interest
170    ///
171    /// Applies accrued interest to total borrows and reserves.
172    ///
173    access(all) fun accrueInterest() {
174        // Return early if accrue 0 interest
175        if (UInt256(getCurrentBlock().height) == self.accrualBlockNumber) {
176            return
177        }
178        let scaledCashPrior = self.getPoolCash()
179        let scaledBorrowPrior = self.scaledTotalBorrows
180        
181        let res = self.accrueInterestReadonly()
182        
183        // Write calculated values into contract storage
184        self.accrualBlockNumber = res[0]
185        self.scaledBorrowIndex = res[1]
186        self.scaledTotalBorrows = res[2]
187        self.scaledTotalReserves = res[3]
188
189        emit AccrueInterest(scaledCashPrior, res[2]-scaledBorrowPrior, self.scaledBorrowIndex, self.scaledTotalBorrows)
190    }
191
192    /// Calculates the exchange rate from the underlying to virtual lpToken (i.e. how many UnderlyingToken per virtual lpToken)
193    /// Note: It doesn't call accrueInterest() first to update with latest states which is used in calculating the exchange rate.
194    ///
195    access(all) view fun underlyingToLpTokenRateSnapshotScaled(): UInt256 {
196        if (self.scaledTotalSupply == 0) {
197            return self.scaledInitialExchangeRate
198        } else {
199            return (self.getPoolCash() + self.scaledTotalBorrows - self.scaledTotalReserves) * LendingConfig.scaleFactor / self.scaledTotalSupply
200        }
201    }
202
203    /// Calculates the scaled borrow balance of borrower address based on stored states
204    /// Note: It doesn't call accrueInterest() first to update with latest states which is used in calculating the borrow balance.
205    ///
206    access(all) view fun borrowBalanceSnapshotScaled(borrowerAddress: Address): UInt256 {
207        if (self.accountBorrows.containsKey(borrowerAddress) == false) {
208            return 0
209        }
210        let borrower = self.accountBorrows[borrowerAddress]!
211        return borrower.scaledPrincipal * self.scaledBorrowIndex / borrower.scaledInterestIndex
212    }
213
214    /// Supplier deposits underlying asset's Vault into the pool.
215    ///
216    /// @Param SupplierAddr - The address of the account which is supplying the assets
217    /// @Param InUnderlyingVault - The vault for deposit and its type should match the pool's underlying token type 
218    ///
219    /// Interest will be accrued up to the current block.
220    /// The lending pool will mint the corresponding lptoken according to the current
221    /// exchange rate of lptoken as the user's deposit certificate and save it in the contract.
222    /// 
223    access(all) fun supply(supplierAddr: Address, inUnderlyingVault: @{FungibleToken.Vault}) {
224        pre {
225            inUnderlyingVault.balance > 0.0: 
226                LendingError.ErrorEncode(msg: "Supplied zero", err: LendingError.ErrorCode.EMPTY_FUNGIBLE_TOKEN_VAULT)
227            inUnderlyingVault.isInstance(self.underlyingAssetType):
228                LendingError.ErrorEncode(
229                    msg: "Supplied vault and pool underlying type mismatch",
230                    err: LendingError.ErrorCode.MISMATCHED_INPUT_VAULT_TYPE_WITH_POOL
231                )
232        }
233
234        // 1. Accrues interests and checkpoints latest states
235        self.accrueInterest()
236
237        // 2. Check whether or not supplyAllowed()
238        let scaledAmount = LendingConfig.UFix64ToScaledUInt256(inUnderlyingVault.balance)
239        let err = self.comptrollerCap!.borrow()!.supplyAllowed(
240            poolCertificate: <- create PoolCertificate(),
241            poolAddress: self.poolAddress,
242            supplierAddress: supplierAddr,
243            supplyUnderlyingAmountScaled: scaledAmount
244        )
245        assert(err == nil, message: err ?? "")
246        
247        // 3. Deposit into underlying vault and mint corresponding PoolTokens 
248        let underlyingToken2LpTokenRateScaled = self.underlyingToLpTokenRateSnapshotScaled()
249        let scaledMintVirtualAmount = scaledAmount * LendingConfig.scaleFactor / underlyingToken2LpTokenRateScaled
250        // mint pool tokens for supply certificate
251        self.accountLpTokens[supplierAddr] = scaledMintVirtualAmount + (self.accountLpTokens[supplierAddr] ?? 0)
252        self.scaledTotalSupply = self.scaledTotalSupply + scaledMintVirtualAmount
253        self.underlyingVault.deposit(from: <-inUnderlyingVault)
254
255        emit Supply(supplier: supplierAddr, scaledSuppliedUnderlyingAmount: scaledAmount, scaledMintedLpTokenAmount: scaledMintVirtualAmount)
256    }
257
258    /// Redeems lpTokens for the underlying asset's vault
259    /// or
260    /// Redeems lpTokens for a specified amount of underlying asset
261    ///
262    /// @Param redeemer - The address of the account which is redeeming the tokens
263    /// @Param numLpTokenToRedeem - The number of lpTokens to redeem into underlying (only one of numLpTokenToRedeem or numUnderlyingToRedeem may be non-zero)
264    /// @Param numUnderlyingToRedeem - The amount of underlying to receive from redeeming lpTokens
265    /// @Return The redeemed vault resource of pool's underlying token
266    ///
267    /// Since redeemer decreases his overall collateral ratio across all markets, safety check happenes inside comptroller.
268    ///
269    access(self) fun redeemInternal(
270        redeemer: Address,
271        numLpTokenToRedeem: UFix64,
272        numUnderlyingToRedeem: UFix64
273    ): @{FungibleToken.Vault} {
274        pre {
275            numLpTokenToRedeem == 0.0 || numUnderlyingToRedeem == 0.0:
276                LendingError.ErrorEncode(
277                    msg: "numLpTokenToRedeem or numUnderlyingToRedeem must be 0.0.",
278                    err: LendingError.ErrorCode.INVALID_PARAMETERS
279                )
280            self.accountLpTokens.containsKey(redeemer):
281                LendingError.ErrorEncode(
282                    msg: "redeemer has no supply to redeem from",
283                    err: LendingError.ErrorCode.REDEEM_FAILED_NO_ENOUGH_LP_TOKEN
284                )
285        }
286
287        // 1. Accrues interests and checkpoints latest states
288        self.accrueInterest()
289
290        // 2. Check whether or not redeemAllowed()
291        var scaledLpTokenToRedeem: UInt256 = 0
292        var scaledUnderlyingToRedeem: UInt256 = 0
293        let scaledUnderlyingToLpRate = self.underlyingToLpTokenRateSnapshotScaled()
294        let scaleFactor = LendingConfig.scaleFactor
295        if (numLpTokenToRedeem == 0.0) {
296            // redeem all
297            // the special value of `UFIx64.max` indicating to redeem all virtual LP tokens the redeemer has
298            if numUnderlyingToRedeem == UFix64.max {
299                scaledLpTokenToRedeem = self.accountLpTokens[redeemer]!
300                scaledUnderlyingToRedeem = scaledLpTokenToRedeem * scaledUnderlyingToLpRate / scaleFactor
301            } else {
302                scaledLpTokenToRedeem = LendingConfig.UFix64ToScaledUInt256(numUnderlyingToRedeem) * scaleFactor / scaledUnderlyingToLpRate
303                scaledUnderlyingToRedeem = LendingConfig.UFix64ToScaledUInt256(numUnderlyingToRedeem)
304            }
305        } else {
306            if numLpTokenToRedeem == UFix64.max {
307                scaledLpTokenToRedeem = self.accountLpTokens[redeemer]!
308            } else {
309                scaledLpTokenToRedeem = LendingConfig.UFix64ToScaledUInt256(numLpTokenToRedeem)
310            }
311            scaledUnderlyingToRedeem = scaledLpTokenToRedeem * scaledUnderlyingToLpRate / scaleFactor
312        }
313
314        assert(scaledLpTokenToRedeem <= self.accountLpTokens[redeemer]!, message: 
315            LendingError.ErrorEncode(
316                msg: "exceeded redeemer lp token balance",
317                err: LendingError.ErrorCode.REDEEM_FAILED_NO_ENOUGH_LP_TOKEN
318            )
319        )
320
321        let err = self.comptrollerCap!.borrow()!.redeemAllowed(
322            poolCertificate: <- create PoolCertificate(),
323            poolAddress: self.poolAddress,
324            redeemerAddress: redeemer,
325            redeemLpTokenAmountScaled: scaledLpTokenToRedeem,
326        )
327        assert(err == nil, message: err ?? "")
328        
329        // 3. Burn virtual lpTokens, withdraw from underlying vault and return it
330        assert(scaledUnderlyingToRedeem <= self.getPoolCash(), message:
331            LendingError.ErrorEncode(
332                msg: "insufficient pool liquidity to redeem",
333                err: LendingError.ErrorCode.INSUFFICIENT_POOL_LIQUIDITY
334            )
335        )
336
337        self.scaledTotalSupply = self.scaledTotalSupply - scaledLpTokenToRedeem
338        if (self.accountLpTokens[redeemer] == scaledLpTokenToRedeem) {
339            self.accountLpTokens.remove(key: redeemer)
340        } else {
341            self.accountLpTokens[redeemer] = self.accountLpTokens[redeemer]! - scaledLpTokenToRedeem
342        }
343        emit Redeem(
344            redeemer: redeemer,
345            scaledLpTokenToRedeem: scaledLpTokenToRedeem,
346            scaledRedeemedUnderlyingAmount: scaledUnderlyingToRedeem
347        )
348        let amountUnderlyingToRedeem = LendingConfig.ScaledUInt256ToUFix64(scaledUnderlyingToRedeem)
349        return <- self.underlyingVault.withdraw(amount: amountUnderlyingToRedeem)
350    }
351
352    /// User redeems lpTokens for the underlying asset's vault
353    ///
354    /// @Param userCertificate - User identity certificate and it can provide a valid user address proof
355    /// @Param numLpTokenToRedeem - The number of lpTokens to redeem into underlying
356    /// @Return The redeemed vault resource of pool's underlying token
357    ///
358    /// @Notice It is more convenient to use a resource certificate on flow for authentication than signing a signature.
359    ///
360    /// RedeemerAddress is inferred from IdentityCertificate resource, which is stored in user account and can only be given by its owner.
361    /// The special value of numLpTokenToRedeem `UFIx64.max` indicating to redeem all virtual LP tokens the redeemer has.
362    ///
363    access(all) fun redeem(
364        userCertificate: &{LendingInterfaces.IdentityCertificate},
365        numLpTokenToRedeem: UFix64
366    ): @{FungibleToken.Vault} {
367        pre {
368            numLpTokenToRedeem > 0.0: LendingError.ErrorEncode(msg: "Redeemed zero", err: LendingError.ErrorCode.INVALID_PARAMETERS)
369            userCertificate.owner != nil:
370                LendingError.ErrorEncode(
371                    msg: "Cannot borrow reference to IdentityCertificate",
372                    err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE
373                )
374            self.checkUserCertificateType(cert: userCertificate):
375                LendingError.ErrorEncode(
376                    msg: "Certificate not issued by system",
377                    err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE
378                )
379        }
380        
381        let redeemerAddress = userCertificate.owner!.address
382        let res <- self.redeemInternal(
383            redeemer: redeemerAddress,
384            numLpTokenToRedeem: numLpTokenToRedeem,
385            numUnderlyingToRedeem: 0.0
386        )
387
388        return <- res
389    }
390
391    /// User redeems lpTokens for a specified amount of underlying asset
392    ///
393    /// @Param userCertificate - User identity certificate and it can provide a valid user address proof
394    /// @Param numUnderlyingToRedeem - The amount of underlying to receive from redeeming lpTokens
395    /// @Return The redeemed vault resource of pool's underlying token
396    ///
397    /// @Notice It is more convenient to use a resource certificate on flow for authentication than signing a signature.
398    ///
399    /// RedeemerAddress is inferred from the private capability to the IdentityCertificate resource,
400    /// which is stored in user account and can only be given by its owner.
401    /// The special value of numUnderlyingToRedeem `UFIx64.max` indicating to redeem all the underlying liquidity.
402    ///
403    access(all) fun redeemUnderlying(
404        userCertificate: &{LendingInterfaces.IdentityCertificate},
405        numUnderlyingToRedeem: UFix64
406    ): @{FungibleToken.Vault} {
407        pre {
408            numUnderlyingToRedeem > 0.0: LendingError.ErrorEncode(msg: "Redeemed zero", err: LendingError.ErrorCode.INVALID_PARAMETERS)
409            userCertificate.owner != nil:
410                LendingError.ErrorEncode(
411                    msg: "Cannot borrow reference to IdentityCertificate",
412                    err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE
413                )
414            self.checkUserCertificateType(cert: userCertificate):
415                LendingError.ErrorEncode(
416                    msg: "Certificate not issued by system",
417                    err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE
418                )
419        }        
420        let redeemerAddress = userCertificate.owner!.address
421        let res <- self.redeemInternal(
422            redeemer: redeemerAddress,
423            numLpTokenToRedeem: 0.0,
424            numUnderlyingToRedeem: numUnderlyingToRedeem
425        )
426
427        return <- res
428    }
429
430    /// User borrows underlying asset from the pool.
431    ///
432    /// @Param userCertificate - User identity certificate and it can provide a valid user address proof
433    /// @Param borrowAmount - The amount of the underlying asset to borrow
434    /// @Return The vault of borrow asset
435    ///
436    /// @Notice It is more convenient to use a resource certificate on flow for authentication than signing a signature.
437    ///
438    /// Since borrower would decrease his overall collateral ratio across all markets, safety check happenes inside comptroller
439    ///
440    access(all) fun borrow(
441        userCertificate: &{LendingInterfaces.IdentityCertificate},
442        borrowAmount: UFix64,
443    ): @{FungibleToken.Vault} {
444        pre {
445            borrowAmount > 0.0: LendingError.ErrorEncode(msg: "borrowAmount zero", err: LendingError.ErrorCode.INVALID_PARAMETERS)
446            userCertificate.owner != nil:
447                LendingError.ErrorEncode(
448                    msg: "Cannot borrow reference to IdentityCertificate",
449                    err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE
450                )
451            self.checkUserCertificateType(cert: userCertificate):
452                LendingError.ErrorEncode(
453                    msg: "Certificate not issued by system",
454                    err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE
455                )
456        }
457        // 1. Accrues interests and checkpoints latest states
458        self.accrueInterest()
459
460        // 2. Pool liquidity check
461        let scaledBorrowAmount = LendingConfig.UFix64ToScaledUInt256(borrowAmount)
462        assert(scaledBorrowAmount <= self.getPoolCash(), message:
463            LendingError.ErrorEncode(
464                msg: "insufficient pool liquidity to borrow",
465                err: LendingError.ErrorCode.INSUFFICIENT_POOL_LIQUIDITY
466            )
467        )
468
469        // 3. Check whether or not borrowAllowed()
470        let borrower = userCertificate.owner!.address
471        let err = self.comptrollerCap!.borrow()!.borrowAllowed(
472            poolCertificate: <- create PoolCertificate(),
473            poolAddress: self.poolAddress,
474            borrowerAddress: borrower,
475            borrowUnderlyingAmountScaled: scaledBorrowAmount
476        )
477        assert(err == nil, message: err ?? "")
478        
479        // 4. Updates borrow states, withdraw from pool underlying vault and deposits into borrower's account
480        self.scaledTotalBorrows = self.scaledTotalBorrows + scaledBorrowAmount
481        let scaledBorrowBalanceNew = scaledBorrowAmount + self.borrowBalanceSnapshotScaled(borrowerAddress: borrower)
482        self.accountBorrows[borrower] = BorrowSnapshot(principal: scaledBorrowBalanceNew, interestIndex: self.scaledBorrowIndex)
483        emit Borrow(borrower: borrower, scaledBorrowAmount: scaledBorrowAmount, scaledBorrowerTotalBorrows: scaledBorrowBalanceNew, scaledPoolTotalBorrows: self.scaledTotalBorrows)
484        
485        let res <- self.underlyingVault.withdraw(amount: borrowAmount)
486        return <- res
487    }
488
489    /// Repay the borrower's borrow
490    ///
491    /// @Param borrower - The address of the borrower
492    /// @Param borrowAmount - The amount to repay
493    /// @Return The overpaid vault will be returned.
494    ///
495    /// @Note: Caller ensures that LendingPool.accrueInterest() has been called with latest states checkpointed
496    ///
497    access(self) fun repayBorrowInternal(borrower: Address, repayUnderlyingVault: @{FungibleToken.Vault}): @{FungibleToken.Vault}? {
498        // Check whether or not repayAllowed()
499        let scaledRepayAmount = LendingConfig.UFix64ToScaledUInt256(repayUnderlyingVault.balance)
500        let scaledAccountTotalBorrows = self.borrowBalanceSnapshotScaled(borrowerAddress: borrower)
501        let scaledActualRepayAmount = scaledAccountTotalBorrows > scaledRepayAmount ? scaledRepayAmount : scaledAccountTotalBorrows
502        
503        let err = self.comptrollerCap!.borrow()!.repayAllowed(
504            poolCertificate: <- create PoolCertificate(),
505            poolAddress: self.poolAddress,
506            borrowerAddress: borrower,
507            repayUnderlyingAmountScaled: scaledActualRepayAmount
508        )
509        assert(err == nil, message: err ?? "")
510        
511        // Updates borrow states, deposit repay Vault into pool underlying vault and return any remaining Vault
512        let scaledAccountTotalBorrowsNew = scaledAccountTotalBorrows > scaledRepayAmount ? scaledAccountTotalBorrows - scaledRepayAmount : (0 as UInt256)
513        self.scaledTotalBorrows = self.scaledTotalBorrows - scaledActualRepayAmount
514        emit Repay(borrower: borrower, scaledActualRepayAmount: scaledActualRepayAmount, scaledBorrowerTotalBorrows: scaledAccountTotalBorrowsNew, scaledPoolTotalBorrows: self.scaledTotalBorrows)
515        if (scaledAccountTotalBorrows > scaledRepayAmount) {
516            self.accountBorrows[borrower] = BorrowSnapshot(principal: scaledAccountTotalBorrowsNew, interestIndex: self.scaledBorrowIndex)
517            self.underlyingVault.deposit(from: <-repayUnderlyingVault)
518            return nil
519        } else {
520            self.accountBorrows.remove(key: borrower)
521            let surplusAmount = LendingConfig.ScaledUInt256ToUFix64(scaledRepayAmount - scaledAccountTotalBorrows)
522            self.underlyingVault.deposit(from: <-repayUnderlyingVault)
523            return <- self.underlyingVault.withdraw(amount: surplusAmount)
524        }
525    }
526
527    /// Anyone can repay borrow with a underlying Vault and receives a new underlying Vault if there's still any remaining left.
528    ///
529    /// @Param borrower - The address of the borrower
530    /// @Param borrowAmount - The amount to repay
531    /// @Return The overpaid vault will be returned.
532    ///
533    /// @Note: Note that the borrower address can potentially not be the same as the repayer address (which means someone can repay on behave of borrower),
534    ///        this is allowed as there's no safety issue to do so.
535    ///
536    access(all) fun repayBorrow(borrower: Address, repayUnderlyingVault: @{FungibleToken.Vault}): @{FungibleToken.Vault}? {
537        pre {
538            repayUnderlyingVault.balance > 0.0: LendingError.ErrorEncode(msg: "Repaid zero", err: LendingError.ErrorCode.EMPTY_FUNGIBLE_TOKEN_VAULT)
539            repayUnderlyingVault.isInstance(self.underlyingAssetType):
540                LendingError.ErrorEncode(
541                    msg: "Repaid vault and pool underlying type mismatch",
542                    err: LendingError.ErrorCode.MISMATCHED_INPUT_VAULT_TYPE_WITH_POOL
543                )
544        }
545        // Accrues interests and checkpoints latest states
546        self.accrueInterest()
547
548        let res <- self.repayBorrowInternal(borrower: borrower, repayUnderlyingVault: <-repayUnderlyingVault)
549
550        return <- res
551    }
552
553    /// Liquidates the borrowers collateral.
554    ///
555    /// @Param liquidator - The address of the liquidator who will receive the collateral lpToken transfer
556    /// @Param borrower - The borrower to be liquidated
557    /// @Param poolCollateralizedToSeize - The market address in which to seize collateral from the borrower
558    /// @Param repayUnderlyingVault - The amount of the underlying borrowed asset in this pool to repay
559    /// @Return The overLiquidate vault will be returned.
560    ///
561    /// The collateral lpTokens seized is transferred to the liquidator.
562    ///
563    access(all) fun liquidate(
564        liquidator: Address,
565        borrower: Address,
566        poolCollateralizedToSeize: Address,
567        repayUnderlyingVault: @{FungibleToken.Vault}
568    ): @{FungibleToken.Vault}? {
569        pre {
570            repayUnderlyingVault.balance > 0.0: LendingError.ErrorEncode(msg: "Liquidator repaid zero", err: LendingError.ErrorCode.EMPTY_FUNGIBLE_TOKEN_VAULT)
571            repayUnderlyingVault.isInstance(self.underlyingAssetType):
572                LendingError.ErrorEncode(
573                    msg: "Liquidator repaid vault and pool underlying type mismatch",
574                    err: LendingError.ErrorCode.MISMATCHED_INPUT_VAULT_TYPE_WITH_POOL
575                )
576        }
577        // 1. Accrues interests and checkpoints latest states
578        self.accrueInterest()
579
580        // 2. Check whether or not liquidateAllowed()
581        let scaledUnderlyingAmountToRepay = LendingConfig.UFix64ToScaledUInt256(repayUnderlyingVault.balance)
582        let err = self.comptrollerCap!.borrow()!.liquidateAllowed(
583            poolCertificate: <- create PoolCertificate(),
584            poolBorrowed: self.poolAddress,
585            poolCollateralized: poolCollateralizedToSeize,
586            borrower: borrower,
587            repayUnderlyingAmountScaled: scaledUnderlyingAmountToRepay
588        )
589        assert(err == nil, message: err ?? "")
590
591        // 3. Liquidator repays on behave of borrower
592        assert(liquidator != borrower, message:
593            LendingError.ErrorEncode(
594                msg: "liquidator and borrower cannot be the same",
595                err: LendingError.ErrorCode.SAME_LIQUIDATOR_AND_BORROWER
596            )
597        )
598        let remainingVault <- self.repayBorrowInternal(borrower: borrower, repayUnderlyingVault: <-repayUnderlyingVault)
599
600        let scaledRemainingAmount = LendingConfig.UFix64ToScaledUInt256(remainingVault?.balance ?? 0.0)
601        let scaledActualRepayAmount = scaledUnderlyingAmountToRepay - scaledRemainingAmount
602        // Calculate collateralLpTokenSeizedAmount based on actualRepayAmount
603        let scaledCollateralLpTokenSeizedAmount = self.comptrollerCap!.borrow()!.calculateCollateralPoolLpTokenToSeize(
604            borrower: borrower,
605            borrowPool: self.poolAddress,
606            collateralPool: poolCollateralizedToSeize,
607            actualRepaidBorrowAmountScaled: scaledActualRepayAmount
608        )
609
610        // 4. seizeInternal if current pool is also borrower's collateralPool; otherwise seize external collateralPool
611        if (poolCollateralizedToSeize == self.poolAddress) {
612            self.seizeInternal(
613                liquidator: liquidator,
614                borrower: borrower,
615                scaledBorrowerLpTokenToSeize: scaledCollateralLpTokenSeizedAmount
616            )
617        } else {
618            // Seize external
619            let externalPoolPublicRef = getAccount(poolCollateralizedToSeize)
620                .capabilities.borrow<&{LendingInterfaces.PoolPublic}>(LendingConfig.PoolPublicPublicPath)
621                    ?? panic(
622                        LendingError.ErrorEncode(
623                            msg: "Cannot borrow reference to external PoolPublic resource",
624                            err: LendingError.ErrorCode.CANNOT_ACCESS_POOL_PUBLIC_CAPABILITY
625                        ) 
626                    )
627            externalPoolPublicRef.seize(
628                seizerPoolCertificate: <- create PoolCertificate(),
629                seizerPool: self.poolAddress,
630                liquidator: liquidator,
631                borrower: borrower,
632                scaledBorrowerCollateralLpTokenToSeize: scaledCollateralLpTokenSeizedAmount
633            )
634        }
635
636        emit Liquidate(
637            liquidator: liquidator,
638            borrower: borrower,
639            scaledActualRepaidUnderlying: scaledActualRepayAmount,
640            collateralPoolToSeize: poolCollateralizedToSeize,
641            scaledCollateralPoolLpTokenSeized: scaledCollateralLpTokenSeizedAmount
642        )
643
644        return <-remainingVault
645    }
646
647    /// External seize, transfers collateral tokens (this market) to the liquidator.
648    ///
649    /// @Param seizerPoolCertificate - Pool's certificate guarantee that this interface can only be called by other valid markets
650    /// @Param seizerPool - The external pool seizing the current collateral pool (i.e. borrowPool)
651    /// @Param liquidator - The address of the liquidator who will receive the collateral lpToken transfer
652    /// @Param borrower - The borrower to be liquidated
653    /// @Param scaledBorrowerCollateralLpTokenToSeize - The amount of collateral lpTokens that will be seized from borrower to liquidator
654    ///
655    /// Only used for "external" seize. Run-time type check of pool certificate ensures it can only be called by other supported markets.
656    ///
657    access(all) fun seize(
658        seizerPoolCertificate: @{LendingInterfaces.IdentityCertificate},
659        seizerPool: Address,
660        liquidator: Address,
661        borrower: Address,
662        scaledBorrowerCollateralLpTokenToSeize: UInt256
663    ) {
664        pre {
665            seizerPool != self.poolAddress:
666                LendingError.ErrorEncode(
667                    msg: "External seize only, seizerPool cannot be current pool",
668                    err: LendingError.ErrorCode.EXTERNAL_SEIZE_FROM_SELF
669                )
670        }
671        // 1. Check and verify caller from another LendingPool contract
672        let err = self.comptrollerCap!.borrow()!.callerAllowed(
673            callerCertificate: <- seizerPoolCertificate,
674            callerAddress: seizerPool
675        )
676        assert(err == nil, message: err ?? "")
677
678        // 2. Accrues interests and checkpoints latest states
679        self.accrueInterest()
680
681        // 3. seizeInternal
682        self.seizeInternal(
683            liquidator: liquidator,
684            borrower: borrower,
685            scaledBorrowerLpTokenToSeize: scaledBorrowerCollateralLpTokenToSeize
686        )
687    }
688
689    /// Internal seize
690    ///
691    /// @Param liquidator - The address of the liquidator who will receive the collateral lpToken transfer
692    /// @Param borrower - The borrower to be liquidated
693    /// @Param scaledBorrowerLpTokenToSeize - The amount of collateral lpTokens that will be seized from borrower to liquidator
694    ///
695    /// Caller ensures accrueInterest() has been called
696    ///
697    access(self) fun seizeInternal(
698        liquidator: Address,
699        borrower: Address,
700        scaledBorrowerLpTokenToSeize: UInt256
701    ) {
702        pre {
703            liquidator != borrower:
704                LendingError.ErrorEncode(
705                    msg: "seize: liquidator == borrower",
706                    err: LendingError.ErrorCode.SAME_LIQUIDATOR_AND_BORROWER
707                )
708        }
709        
710        let err = self.comptrollerCap!.borrow()!.seizeAllowed(
711            poolCertificate: <- create PoolCertificate(),
712            borrowPool: self.poolAddress,
713            collateralPool: self.poolAddress,
714            liquidator: liquidator,
715            borrower: borrower,
716            seizeCollateralPoolLpTokenAmountScaled: scaledBorrowerLpTokenToSeize
717        )
718        assert(err == nil, message: err ?? "")
719
720        let scaleFactor = LendingConfig.scaleFactor
721        let scaledProtocolSeizedLpTokens = scaledBorrowerLpTokenToSeize * self.scaledPoolSeizeShare / scaleFactor
722        let scaledLiquidatorSeizedLpTokens = scaledBorrowerLpTokenToSeize - scaledProtocolSeizedLpTokens
723        let scaledUnderlyingToLpTokenRate = self.underlyingToLpTokenRateSnapshotScaled()
724        let scaledAddedUnderlyingReserves = scaledUnderlyingToLpTokenRate * scaledProtocolSeizedLpTokens / scaleFactor
725        self.scaledTotalReserves = self.scaledTotalReserves + scaledAddedUnderlyingReserves
726        self.scaledTotalSupply = self.scaledTotalSupply - scaledProtocolSeizedLpTokens
727        // in-place liquidation: only virtual lpToken records get updated, no token deposit / withdraw needs to happen
728        if (self.accountLpTokens[borrower] == scaledBorrowerLpTokenToSeize) {
729            self.accountLpTokens.remove(key: borrower)
730        } else {
731            self.accountLpTokens[borrower] = self.accountLpTokens[borrower]! - scaledBorrowerLpTokenToSeize
732        }
733        self.accountLpTokens[liquidator] = scaledLiquidatorSeizedLpTokens + (self.accountLpTokens[liquidator] ?? 0)
734
735        emit ReservesAdded(donator: self.poolAddress, scaledAddedUnderlyingAmount: scaledAddedUnderlyingReserves, scaledNewTotalReserves: self.scaledTotalReserves)
736    }
737
738    /// An executor contract can request to use the whole liquidity of current LendingPool and perform custom operations (like arbitrage, liquidation, et al.), as long as:
739    ///   1. executor implements FlashLoanExecutor resource interface and sets up corresponding resource to receive & process requested tokens, and
740    ///   2. executor repays back requested amount + fees (dominated by 'flashloanRateBps x amount'), and
741    /// all in one atomic function call.
742    /// @params: User-definited extra data passed to executor for further auth/check/decode
743    ///
744    access(all) fun flashloan(executor: &{LendingInterfaces.FlashLoanExecutor}, requestedAmount: UFix64, params: {String: AnyStruct}) {
745        pre {
746            self.isFlashloanOpen(): LendingError.ErrorEncode(
747                    msg: "LendingError: flashloan is not open",
748                    err: LendingError.ErrorCode.MARKET_NOT_OPEN
749                )
750            requestedAmount > 0.0 && requestedAmount < self.underlyingVault.balance: LendingError.ErrorEncode(
751                    msg: "LendingError: flashloan invalid requested amount",
752                    err: LendingError.ErrorCode.INSUFFICIENT_POOL_LIQUIDITY
753                )
754        }
755        // Accrues interests and checkpoints latest states
756        self.accrueInterest()
757
758        let requestedAmountScaled = LendingConfig.UFix64ToScaledUInt256(requestedAmount)
759        let tokenOut <-self.underlyingVault.withdraw(amount: requestedAmount)
760        self.scaledTotalBorrows = self.scaledTotalBorrows + requestedAmountScaled
761
762        let tokenIn <- executor.executeAndRepay(loanedToken: <- tokenOut, params: params)
763        assert(tokenIn.isInstance(self.underlyingAssetType), message:
764            LendingError.ErrorEncode(
765                msg: "LendingError: flashloan repaid incompatible token",
766                err: LendingError.ErrorCode.INVALID_PARAMETERS
767            )
768        )
769        assert(tokenIn.balance >= requestedAmount * (1.0 + UFix64(self.getFlashloanRateBps()) / 10000.0), message:
770            LendingError.ErrorEncode(
771                msg: "LendingError: flashloan insufficient repayment",
772                err: LendingError.ErrorCode.INVALID_PARAMETERS
773            )
774        )
775
776        self.underlyingVault.deposit(from: <- tokenIn)
777        self.scaledTotalBorrows = self.scaledTotalBorrows - requestedAmountScaled
778
779        emit Flashloan(executor: executor.owner!.address, executorType: executor.getType(), originator: self.account.address, amount: requestedAmount)
780    }
781
782    /// Check whether or not the given certificate is issued by system
783    ///
784    access(self) view fun checkUserCertificateType(cert: &{LendingInterfaces.IdentityCertificate}): Bool {
785        return cert.isInstance(self.comptrollerCap!.borrow()!.getUserCertificateType())
786    }
787
788    access(all) view fun getFlashloanRateBps(): UInt64 {
789        return (self._reservedFields["flashloanRateBps"] as! UInt64?) ?? 5
790    }
791
792    access(all) view fun isFlashloanOpen(): Bool {
793        return (self._reservedFields["isFlashloanOpen"] as! Bool?) ?? false
794    }
795
796    /// PoolCertificate
797    ///
798    /// Inherited from IdentityCertificate.
799    /// Proof of identity for the pool.
800    ///
801    access(all) resource PoolCertificate: LendingInterfaces.IdentityCertificate {}
802
803    /// PoolPublic
804    ///
805    /// The external interfaces of the pool, and will be exposed as a public capability.
806    ///
807    access(all) resource PoolPublic: LendingInterfaces.PoolPublic {
808
809        access(all) view fun getPoolAddress(): Address {
810            return LendingPool.poolAddress
811        }
812
813        access(all) view fun getUnderlyingTypeString(): String {
814            let underlyingType = LendingPool.getUnderlyingAssetType()
815            // "A.1654653399040a61.FlowToken.Vault" => "FlowToken"
816            return underlyingType.slice(from: 19, upTo: underlyingType.length - 6)
817        }
818
819        access(all) view fun getUnderlyingAssetType(): String {
820            return LendingPool.getUnderlyingAssetType()
821        }
822
823        access(all) view fun getUnderlyingToLpTokenRateScaled(): UInt256 {
824            return LendingPool.underlyingToLpTokenRateSnapshotScaled()
825        }
826
827        access(all) view fun getAccountLpTokenBalanceScaled(account: Address): UInt256 {
828            return LendingPool.accountLpTokens[account] ?? 0
829        }
830
831        access(all) view fun getAccountBorrowBalanceScaled(account: Address): UInt256 {
832            return LendingPool.borrowBalanceSnapshotScaled(borrowerAddress: account)
833        }
834
835        access(all) view fun getAccountBorrowPrincipalSnapshotScaled(account: Address): UInt256 {
836            if (LendingPool.accountBorrows.containsKey(account) == false) {
837                return 0
838            } else {
839                return LendingPool.accountBorrows[account]!.scaledPrincipal
840            }
841        }
842
843        access(all) view fun getAccountBorrowIndexSnapshotScaled(account: Address): UInt256 {
844            if (LendingPool.accountBorrows.containsKey(account) == false) {
845                return 0
846            } else {
847                return LendingPool.accountBorrows[account]!.scaledInterestIndex
848            }
849        }
850
851        access(all) view fun getAccountSnapshotScaled(account: Address): [UInt256; 5] {
852            return [
853                self.getUnderlyingToLpTokenRateScaled(),
854                self.getAccountLpTokenBalanceScaled(account: account),
855                self.getAccountBorrowBalanceScaled(account: account),
856                self.getAccountBorrowPrincipalSnapshotScaled(account: account),
857                self.getAccountBorrowIndexSnapshotScaled(account: account)
858            ]
859        }
860
861        access(all) view fun getAccountRealtimeScaled(account: Address): [UInt256; 5] {
862            let accrueInterestRealtimeRes = self.accrueInterestReadonly()
863            let poolBorrowIndexRealtime = accrueInterestRealtimeRes[1]
864            let poolTotalBorrowRealtime = accrueInterestRealtimeRes[2]
865            let poolTotalReserveRealtime = accrueInterestRealtimeRes[3]
866
867            let underlyingTolpTokenRateRealtime = (LendingPool.getPoolCash() + poolTotalBorrowRealtime - poolTotalReserveRealtime) * LendingConfig.scaleFactor / LendingPool.scaledTotalSupply
868            
869            var borrowBalanceRealtimeScaled:UInt256 = 0
870            if (LendingPool.accountBorrows.containsKey(account)) {
871                borrowBalanceRealtimeScaled = self.getAccountBorrowPrincipalSnapshotScaled(account: account) * poolBorrowIndexRealtime / self.getAccountBorrowIndexSnapshotScaled(account: account)
872            }
873
874            return [
875                underlyingTolpTokenRateRealtime,
876                self.getAccountLpTokenBalanceScaled(account: account),
877                borrowBalanceRealtimeScaled,
878                self.getAccountBorrowPrincipalSnapshotScaled(account: account),
879                self.getAccountBorrowIndexSnapshotScaled(account: account)
880            ]
881        }
882
883        access(all) view fun getPoolReserveFactorScaled(): UInt256 {
884            return LendingPool.scaledReserveFactor
885        }
886
887        access(all) view fun getInterestRateModelAddress(): Address {
888            return LendingPool.interestRateModelAddress!
889        }
890
891        access(all) view fun getPoolTotalBorrowsScaled(): UInt256 {
892            return LendingPool.scaledTotalBorrows
893        }
894
895        access(all) view fun getPoolAccrualBlockNumber(): UInt256 {
896            return LendingPool.accrualBlockNumber
897        }
898
899        access(all) view fun getPoolBorrowIndexScaled(): UInt256 {
900            return LendingPool.scaledBorrowIndex
901        }
902
903        access(all) view fun getPoolTotalLpTokenSupplyScaled(): UInt256 {
904            return LendingPool.scaledTotalSupply
905        }
906
907        access(all) view fun getPoolTotalSupplyScaled(): UInt256 {
908            return LendingPool.getPoolCash() + LendingPool.scaledTotalBorrows
909        }
910
911        access(all) view fun getPoolTotalReservesScaled(): UInt256 {
912            return LendingPool.scaledTotalReserves
913        }
914
915        access(all) view fun getPoolCash(): UInt256 {
916            return LendingPool.getPoolCash()
917        }
918
919        access(all) view fun getPoolSupplierCount(): UInt256 {
920            return UInt256(LendingPool.accountLpTokens.length)
921        }
922
923        access(all) view fun getPoolBorrowerCount(): UInt256 {
924            return UInt256(LendingPool.accountBorrows.length)
925        }
926
927        access(all) view fun getPoolSupplierList(): [Address] {
928            return LendingPool.accountLpTokens.keys
929        }
930
931        access(all) view fun getPoolSupplierSlicedList(from: UInt64, to: UInt64): [Address] {
932            pre {
933                from <= to && to < UInt64(LendingPool.accountLpTokens.length):
934                    LendingError.ErrorEncode(
935                        msg: "Index out of range",
936                        err: LendingError.ErrorCode.INVALID_PARAMETERS
937                    )
938            }
939            return LendingPool.accountLpTokens.keys.slice(from: Int(from), upTo: Int(to+1))
940        }
941
942        access(all) view fun getPoolBorrowerList(): [Address] {
943            return LendingPool.accountBorrows.keys
944        }
945
946        access(all) view fun getPoolBorrowerSlicedList(from: UInt64, to: UInt64): [Address] {
947            pre {
948                from <= to && to < UInt64(LendingPool.accountBorrows.length):
949                    LendingError.ErrorEncode(
950                        msg: "Index out of range",
951                        err: LendingError.ErrorCode.INVALID_PARAMETERS
952                    )
953            }
954            return LendingPool.accountBorrows.keys.slice(from: Int(from), upTo: Int(to+1))
955        }
956
957        access(all) view fun getPoolBorrowRateScaled(): UInt256 {
958            return LendingPool.interestRateModelCap!.borrow()!.getBorrowRate(
959                cash: LendingPool.getPoolCash(),
960                borrows: LendingPool.scaledTotalBorrows,
961                reserves: LendingPool.scaledTotalReserves
962            )
963        }
964
965        access(all) view fun getPoolBorrowAprScaled(): UInt256 {
966            let scaledBorrowRatePerBlock =
967                LendingPool.interestRateModelCap!.borrow()!.getBorrowRate(
968                    cash: LendingPool.getPoolCash(),
969                    borrows: LendingPool.scaledTotalBorrows,
970                    reserves: LendingPool.scaledTotalReserves
971                )
972            let blocksPerYear = LendingPool.interestRateModelCap!.borrow()!.getBlocksPerYear()
973            return scaledBorrowRatePerBlock * blocksPerYear
974        }
975
976        access(all) view fun getPoolSupplyAprScaled(): UInt256 {
977            let scaledSupplyRatePerBlock =
978                LendingPool.interestRateModelCap!.borrow()!.getSupplyRate(
979                    cash: LendingPool.getPoolCash(),
980                    borrows: LendingPool.scaledTotalBorrows,
981                    reserves: LendingPool.scaledTotalReserves,
982                    reserveFactor: LendingPool.scaledReserveFactor
983                )
984            let blocksPerYear = LendingPool.interestRateModelCap!.borrow()!.getBlocksPerYear()
985            return scaledSupplyRatePerBlock * blocksPerYear
986        }
987
988        /// The default flashloan rate is 5 bps (0.05%)
989        access(all) view fun getFlashloanRateBps(): UInt64 {
990            return LendingPool.getFlashloanRateBps()
991        }
992
993        access(all) fun accrueInterest() {
994            LendingPool.accrueInterest()
995        }
996
997        access(all) view fun accrueInterestReadonly(): [UInt256; 4] {
998            return LendingPool.accrueInterestReadonly()
999        }
1000
1001        access(all) view fun getPoolCertificateType(): Type {
1002            return Type<@LendingPool.PoolCertificate>()
1003        }
1004
1005        access(all) fun seize(
1006            seizerPoolCertificate: @{LendingInterfaces.IdentityCertificate},
1007            seizerPool: Address,
1008            liquidator: Address,
1009            borrower: Address,
1010            scaledBorrowerCollateralLpTokenToSeize: UInt256
1011        ) {
1012            LendingPool.seize(
1013                seizerPoolCertificate: <- seizerPoolCertificate,
1014                seizerPool: seizerPool,
1015                liquidator: liquidator,
1016                borrower: borrower,
1017                scaledBorrowerCollateralLpTokenToSeize: scaledBorrowerCollateralLpTokenToSeize
1018            )
1019        }
1020
1021        access(all) fun supply(supplierAddr: Address, inUnderlyingVault: @{FungibleToken.Vault}) {
1022            LendingPool.supply(supplierAddr: supplierAddr, inUnderlyingVault: <-inUnderlyingVault)
1023        }
1024
1025        access(all) fun redeem(userCertificate: &{LendingInterfaces.IdentityCertificate}, numLpTokenToRedeem: UFix64): @{FungibleToken.Vault} {
1026            return <-LendingPool.redeem(userCertificate: userCertificate, numLpTokenToRedeem: numLpTokenToRedeem)
1027        }
1028
1029        access(all) fun redeemUnderlying(userCertificate: &{LendingInterfaces.IdentityCertificate}, numUnderlyingToRedeem: UFix64): @{FungibleToken.Vault} {
1030            return <-LendingPool.redeemUnderlying(userCertificate: userCertificate, numUnderlyingToRedeem: numUnderlyingToRedeem)
1031        }
1032
1033        access(all) fun borrow(userCertificate: &{LendingInterfaces.IdentityCertificate}, borrowAmount: UFix64): @{FungibleToken.Vault} {
1034            return <-LendingPool.borrow(userCertificate: userCertificate, borrowAmount: borrowAmount)
1035        }
1036
1037        access(all) fun repayBorrow(borrower: Address, repayUnderlyingVault: @{FungibleToken.Vault}): @{FungibleToken.Vault}? {
1038            return <-LendingPool.repayBorrow(borrower: borrower, repayUnderlyingVault: <-repayUnderlyingVault)
1039        }
1040
1041        access(all) fun liquidate(liquidator: Address, borrower: Address, poolCollateralizedToSeize: Address, repayUnderlyingVault: @{FungibleToken.Vault}): @{FungibleToken.Vault}? {
1042            return <-LendingPool.liquidate(liquidator: liquidator, borrower: borrower, poolCollateralizedToSeize: poolCollateralizedToSeize, repayUnderlyingVault: <-repayUnderlyingVault)
1043        }
1044
1045        access(all) fun flashloan(executor: &{LendingInterfaces.FlashLoanExecutor}, requestedAmount: UFix64, params: {String: AnyStruct}) {
1046            LendingPool.flashloan(executor: executor, requestedAmount: requestedAmount, params: params)
1047        }
1048    }
1049
1050    /// PoolAdmin
1051    ///
1052    access(all) resource PoolAdmin: LendingInterfaces.PoolAdminPublic {
1053        /// Admin function to call accrueInterest() to checkpoint latest states, and then update the interest rate model
1054        access(all) fun setInterestRateModel(newInterestRateModelAddress: Address) {
1055            post {
1056                LendingPool.interestRateModelCap != nil && LendingPool.interestRateModelCap!.check() == true:
1057                    LendingError.ErrorEncode(
1058                        msg: "Invalid contract address of the new interest rate",
1059                        err: LendingError.ErrorCode.CANNOT_ACCESS_INTEREST_RATE_MODEL_CAPABILITY
1060                    )
1061            }
1062            LendingPool.accrueInterest()
1063            
1064            if (newInterestRateModelAddress != LendingPool.interestRateModelAddress) {
1065                let oldInterestRateModelAddress = LendingPool.interestRateModelAddress
1066                LendingPool.interestRateModelAddress = newInterestRateModelAddress
1067                LendingPool.interestRateModelCap = getAccount(newInterestRateModelAddress)
1068                    .capabilities.get<&{LendingInterfaces.InterestRateModelPublic}>(LendingConfig.InterestRateModelPublicPath)
1069                emit NewInterestRateModel(oldInterestRateModelAddress, newInterestRateModelAddress)
1070            }
1071            return
1072        }
1073
1074        /// Admin function to call accrueInterest() to checkpoint latest states, and then update reserveFactor
1075        access(all) fun setReserveFactor(newReserveFactor: UFix64) {
1076            pre {
1077                newReserveFactor <= 1.0:
1078                LendingError.ErrorEncode(
1079                    msg: "Reserve factor out of range 1.0",
1080                    err: LendingError.ErrorCode.INVALID_PARAMETERS
1081                )
1082            }
1083            LendingPool.accrueInterest()
1084            
1085            let oldReserveFactor = LendingConfig.ScaledUInt256ToUFix64(LendingPool.scaledReserveFactor)
1086            LendingPool.scaledReserveFactor = LendingConfig.UFix64ToScaledUInt256(newReserveFactor)
1087
1088            emit NewReserveFactor(oldReserveFactor, newReserveFactor)
1089            return
1090        }
1091
1092        /// Admin function to update poolSeizeShare
1093        access(all) fun setPoolSeizeShare(newPoolSeizeShare: UFix64) {
1094            pre {
1095                newPoolSeizeShare <= 1.0:
1096                LendingError.ErrorEncode(
1097                    msg: "Pool seize share factor out of range 1.0",
1098                    err: LendingError.ErrorCode.INVALID_PARAMETERS
1099                )
1100            }
1101            let oldPoolSeizeShare = LendingConfig.ScaledUInt256ToUFix64(LendingPool.scaledPoolSeizeShare)
1102            LendingPool.scaledPoolSeizeShare = LendingConfig.UFix64ToScaledUInt256(newPoolSeizeShare)
1103
1104            emit NewPoolSeizeShare(oldPoolSeizeShare, newPoolSeizeShare)
1105            return
1106        }
1107
1108        /// Admin function to set comptroller
1109        access(all) fun setComptroller(newComptrollerAddress: Address) {
1110            post {
1111                LendingPool.comptrollerCap != nil && LendingPool.comptrollerCap!.check() == true:
1112                    LendingError.ErrorEncode(
1113                        msg: "Cannot borrow reference to ComptrollerPublic resource",
1114                        err: LendingError.ErrorCode.CANNOT_ACCESS_COMPTROLLER_PUBLIC_CAPABILITY
1115                    )
1116            }
1117            
1118            if (newComptrollerAddress != LendingPool.comptrollerAddress) {
1119                let oldComptrollerAddress = LendingPool.comptrollerAddress
1120                LendingPool.comptrollerAddress = newComptrollerAddress
1121                LendingPool.comptrollerCap = getAccount(newComptrollerAddress)
1122                    .capabilities.get<&{LendingInterfaces.ComptrollerPublic}>(LendingConfig.ComptrollerPublicPath)
1123                emit NewComptroller(oldComptrollerAddress, newComptrollerAddress)
1124            }
1125        }
1126
1127        /// Admin function to initialize pool.
1128        /// Note: can be called only once
1129        access(all) fun initializePool(
1130            reserveFactor: UFix64,
1131            poolSeizeShare: UFix64,
1132            interestRateModelAddress: Address
1133        ) {
1134            pre {
1135                LendingPool.accrualBlockNumber == 0 && LendingPool.scaledBorrowIndex == 0:
1136                    LendingError.ErrorEncode(
1137                        msg: "Pool can only be initialized once",
1138                        err: LendingError.ErrorCode.POOL_INITIALIZED
1139                    )
1140                reserveFactor <= 1.0 && poolSeizeShare <= 1.0:
1141                    LendingError.ErrorEncode(
1142                        msg: "ReserveFactor | poolSeizeShare out of range 1.0",
1143                        err: LendingError.ErrorCode.INVALID_PARAMETERS
1144                    )
1145            }
1146            post {
1147                LendingPool.interestRateModelCap != nil && LendingPool.interestRateModelCap!.check() == true:
1148                    LendingError.ErrorEncode(
1149                        msg: "InterestRateModel not properly initialized",
1150                        err: LendingError.ErrorCode.CANNOT_ACCESS_INTEREST_RATE_MODEL_CAPABILITY
1151                    )
1152            }
1153            LendingPool.accrualBlockNumber = UInt256(getCurrentBlock().height)
1154            LendingPool.scaledBorrowIndex = LendingConfig.scaleFactor
1155            LendingPool.scaledReserveFactor = LendingConfig.UFix64ToScaledUInt256(reserveFactor)
1156            LendingPool.scaledPoolSeizeShare = LendingConfig.UFix64ToScaledUInt256(poolSeizeShare)
1157            LendingPool.interestRateModelAddress = interestRateModelAddress
1158            LendingPool.interestRateModelCap = getAccount(interestRateModelAddress)
1159                .capabilities.get<&{LendingInterfaces.InterestRateModelPublic}>(LendingConfig.InterestRateModelPublicPath)
1160        }
1161
1162        /// Admin function to withdraw pool reserve
1163        access(all) fun withdrawReserves(reduceAmount: UFix64): @{FungibleToken.Vault} {
1164            LendingPool.accrueInterest()
1165            
1166            let reduceAmountScaled = reduceAmount == UFix64.max ? LendingPool.scaledTotalReserves : LendingConfig.UFix64ToScaledUInt256(reduceAmount)
1167            assert(reduceAmountScaled <= LendingPool.scaledTotalReserves, message:
1168                LendingError.ErrorEncode(
1169                    msg: "exceeded pool total reserve",
1170                    err: LendingError.ErrorCode.EXCEED_TOTAL_RESERVES
1171                )
1172            )
1173            assert(reduceAmountScaled <= LendingPool.getPoolCash(), message:
1174                LendingError.ErrorEncode(
1175                    msg: "insufficient pool liquidity to withdraw reserve",
1176                    err: LendingError.ErrorCode.INSUFFICIENT_POOL_LIQUIDITY
1177                )
1178            )
1179            LendingPool.scaledTotalReserves = LendingPool.scaledTotalReserves - reduceAmountScaled
1180            
1181            emit ReservesReduced(scaledReduceAmount: reduceAmountScaled, scaledNewTotalReserves: LendingPool.scaledTotalReserves)
1182
1183            return <- LendingPool.underlyingVault.withdraw(amount: LendingConfig.ScaledUInt256ToUFix64(reduceAmountScaled))
1184        }
1185
1186        access(all) fun setFlashloanRateBps(rateBps: UInt64) {
1187            pre {
1188                rateBps >= 0 && rateBps <= 10000:
1189                    LendingError.ErrorEncode(
1190                        msg: "LendingPool: flashloan rateBps should be in [0, 10000]",
1191                        err: LendingError.ErrorCode.INVALID_PARAMETERS
1192                    )
1193            }
1194            emit FlashloanRateChanged(oldRateBps: LendingPool.getFlashloanRateBps(), newRateBps: rateBps)
1195            LendingPool._reservedFields["flashloanRateBps"] = rateBps
1196        }
1197
1198        access(all) fun setFlashloanOpen(isOpen: Bool) {
1199            emit FlashloanOpen(isOpen: isOpen)
1200            LendingPool._reservedFields["isFlashloanOpen"] = isOpen
1201        }
1202    }
1203
1204    init() {
1205        self.PoolAdminStoragePath = /storage/incrementLendingPoolAdmin
1206        self.UnderlyingAssetVaultStoragePath = /storage/poolUnderlyingAssetVault
1207        self.PoolPublicStoragePath = /storage/incrementLendingPoolPublic
1208        self.PoolPublicPublicPath = /public/incrementLendingPoolPublic
1209
1210        self.poolAddress = self.account.address
1211        self.scaledInitialExchangeRate = LendingConfig.scaleFactor
1212        self.accrualBlockNumber = 0
1213        self.scaledBorrowIndex = 0
1214        self.scaledTotalBorrows = 0
1215        self.scaledTotalReserves = 0
1216        self.scaledReserveFactor = 0
1217        self.scaledPoolSeizeShare = 0
1218        self.scaledTotalSupply = 0
1219        self.accountLpTokens = {}
1220        self.accountBorrows = {}
1221        self.interestRateModelAddress = nil
1222        self.interestRateModelCap = nil
1223        self.comptrollerAddress = nil
1224        self.comptrollerCap = nil
1225        self._reservedFields = {}
1226        self.underlyingVault <- self.account.storage.load<@{FungibleToken.Vault}>(from: self.UnderlyingAssetVaultStoragePath)
1227            ?? panic("Deployer should own zero-balanced underlying asset vault first")
1228        self.underlyingAssetType = self.underlyingVault.getType()
1229        assert(self.underlyingVault.balance == 0.0, message: "Must initialize pool with zero-balanced underlying asset vault")
1230
1231        // save pool admin
1232        destroy <-self.account.storage.load<@AnyResource>(from: self.PoolAdminStoragePath)
1233        self.account.storage.save(<-create PoolAdmin(), to: self.PoolAdminStoragePath)
1234        // save pool public interface
1235        destroy <-self.account.storage.load<@AnyResource>(from: self.PoolPublicStoragePath)
1236        self.account.storage.save(<-create PoolPublic(), to: self.PoolPublicStoragePath)
1237        // Create and link public storage capability
1238        self.account.capabilities.unpublish(self.PoolPublicPublicPath)
1239        self.account.capabilities.publish(
1240            self.account.capabilities.storage.issue<&{LendingInterfaces.PoolPublic}>(self.PoolPublicStoragePath),
1241            at: LendingConfig.PoolPublicPublicPath
1242        )
1243    }
1244}
1245