Smart Contract
LendingPool
A.1113980ca45d1d37.LendingPool
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