DeploySEALED

#○&╱^◆■@□○?▓▪?*▒@╳█&◆*╱▫╱░▒▫▓▫▓◇@╲&▓◆○▫@&█□@~~$▓?◆○●&%▫╱▫$■●$~#◆

Transaction ID

Timestamp

Aug 15, 2024, 01:10:02 PM UTC
1y ago

Block Height

84,605,160

Computation

0

Execution Fee

0.00000399 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
LendingPool
1codeString
/** # The contract implementation of the lending pool. # Author: Increment Labs Core functionalities of the lending pool smart contract supporting cross-market supply, redeem, borrow, repay, and liquidation. Multiple LendingPool contracts will be deployed for each of the different pooled underlying FungibleTokens. */ import FungibleToken from 0xf233dcee88fe0abe import LendingInterfaces from 0x2df970b6cdee5735 import LendingConfig from 0x2df970b6cdee5735 import LendingError from 0x2df970b6cdee5735 pub contract LendingPool { /// Account address the pool is deployed to, i.e. the pool 'contract address' pub let poolAddress: Address /// Initial exchange rate (when LendingPool.totalSupply == 0) between the virtual lpToken and pool underlying token pub let scaledInitialExchangeRate: UInt256 /// Block number that interest was last accrued at pub var accrualBlockNumber: UInt256 /// Accumulator of the total earned interest rate since the opening of the market, scaled up by 1e18 pub var scaledBorrowIndex: UInt256 /// Total amount of outstanding borrows of the underlying in this market, scaled up by 1e18 pub var scaledTotalBorrows: UInt256 /// Total amount of reserves of the underlying held in this market, scaled up by 1e18 pub var scaledTotalReserves: UInt256 /// Total number of virtual lpTokens, scaled up by 1e18 pub var scaledTotalSupply: UInt256 /// Fraction of generated interest added to protocol reserves, scaled up by 1e18 /// Must be in [0.0, 1.0] x scaleFactor pub var scaledReserveFactor: UInt256 /// Share of seized collateral that is added to reserves when liquidation happenes, e.g. 0.028 x 1e18. /// Must be in [0.0, 1.0] x scaleFactor pub var scaledPoolSeizeShare: UInt256 /// { supplierAddress => # of virtual lpToken the supplier owns, scaled up by 1e18 } access(self) let accountLpTokens: {Address: UInt256} /// Reserved parameter fields: {ParamName: Value} /// Used fields: /// |__ 1. "flashloanRateBps" -> UInt64 /// |__ 2. "isFlashloanOpen" -> Bool access(self) let _reservedFields: {String: AnyStruct} /// BorrowSnapshot /// /// Container for borrow balance information /// pub struct BorrowSnapshot { /// Total balance (with accrued interest), after applying the most recent balance-change action pub var scaledPrincipal: UInt256 /// Global borrowIndex as of the most recent balance-change action pub var scaledInterestIndex: UInt256 init(principal: UInt256, interestIndex: UInt256) { self.scaledPrincipal = principal self.scaledInterestIndex = interestIndex } } // { borrowerAddress => BorrowSnapshot } access(self) let accountBorrows: {Address: BorrowSnapshot} /// Model used to calculate underlying asset's borrow interest rate pub var interestRateModelAddress: Address? pub var interestRateModelCap: Capability<&{LendingInterfaces.InterestRateModelPublic}>? /// The address of the comptroller contract pub var comptrollerAddress: Address? pub var comptrollerCap: Capability<&{LendingInterfaces.ComptrollerPublic}>? /// Save underlying asset deposited into this pool access(self) let underlyingVault: @FungibleToken.Vault /// Underlying type access(self) let underlyingAssetType: Type /// Path pub let PoolAdminStoragePath: StoragePath pub let UnderlyingAssetVaultStoragePath: StoragePath pub let PoolPublicStoragePath: StoragePath pub let PoolPublicPublicPath: PublicPath /// Event emitted when interest is accrued pub event AccrueInterest(_ scaledCashPrior: UInt256, _ scaledInterestAccumulated: UInt256, _ scaledBorrowIndexNew: UInt256, _ scaledTotalBorrowsNew: UInt256) /// Event emitted when underlying asset is deposited into pool pub event Supply(supplier: Address, scaledSuppliedUnderlyingAmount: UInt256, scaledMintedLpTokenAmount: UInt256) /// Event emitted when virtual lpToken is burnt and redeemed for underlying asset pub event Redeem(redeemer: Address, scaledLpTokenToRedeem: UInt256, scaledRedeemedUnderlyingAmount: UInt256) /// Event emitted when user borrows underlying from the pool pub event Borrow(borrower: Address, scaledBorrowAmount: UInt256, scaledBorrowerTotalBorrows: UInt256, scaledPoolTotalBorrows: UInt256) /// Event emitted when user repays underlying to pool pub event Repay(borrower: Address, scaledActualRepayAmount: UInt256, scaledBorrowerTotalBorrows: UInt256, scaledPoolTotalBorrows: UInt256) /// Event emitted when pool reserves get added pub event ReservesAdded(donator: Address, scaledAddedUnderlyingAmount: UInt256, scaledNewTotalReserves: UInt256) /// Event emitted when pool reserves is reduced pub event ReservesReduced(scaledReduceAmount: UInt256, scaledNewTotalReserves: UInt256) /// Event emitted when liquidation happenes pub event Liquidate(liquidator: Address, borrower: Address, scaledActualRepaidUnderlying: UInt256, collateralPoolToSeize: Address, scaledCollateralPoolLpTokenSeized: UInt256) /// Event emitted when interestRateModel is changed pub event NewInterestRateModel(_ oldInterestRateModelAddress: Address?, _ newInterestRateModelAddress: Address) /// Event emitted when the reserveFactor is changed pub event NewReserveFactor(_ oldReserveFactor: UFix64, _ newReserveFactor: UFix64) /// Event emitted when the poolSeizeShare is changed pub event NewPoolSeizeShare(_ oldPoolSeizeShare: UFix64, _ newPoolSeizeShare: UFix64) /// Event emitted when the comptroller is changed pub event NewComptroller(_ oldComptrollerAddress: Address?, _ newComptrollerAddress: Address) /// Event emitted when the flashloanRateChanged is changed pub event FlashloanRateChanged(oldRateBps: UInt64, newRateBps: UInt64) /// Event emitted when the isFlashloanOpen is changed pub event FlashloanOpen(isOpen: Bool) /// Event emitted when the flashloan is executed pub event Flashloan(executor: Address, executorType: Type, originator: Address, amount: UFix64) // Return underlying asset's type of current pool pub fun getUnderlyingAssetType(): String { return self.underlyingAssetType.identifier } // Gets current underlying balance of this pool, scaled up by 1e18 pub fun getPoolCash(): UInt256 { return LendingConfig.UFix64ToScaledUInt256(self.underlyingVault.balance) } /// Cal accrue interest /// /// @Return 0. currentBlockNumber - The block number of current calculation of interest /// 1. scaledBorrowIndexNew - The new accumulator of the total earned interest rate since the opening of the market /// 2. scaledTotalBorrowsNew - The new total borrows after accrue interest /// 3. scaledTotalReservesNew - The new total reserves after accrue interest /// /// Calculates interest accrued from the last checkpointed block to the current block /// This function is a readonly function and can be called by scripts. /// pub fun accrueInterestReadonly(): [UInt256; 4] { pre { self.interestRateModelCap != nil && self.interestRateModelCap!.check() == true: LendingError.ErrorEncode( msg: "Cannot borrow reference to InterestRateModel in pool ".concat(LendingPool.poolAddress.toString()), err: LendingError.ErrorCode.CANNOT_ACCESS_INTEREST_RATE_MODEL_CAPABILITY ) } let currentBlockNumber = UInt256(getCurrentBlock().height) let accrualBlockNumberPrior = self.accrualBlockNumber let scaledCashPrior = self.getPoolCash() let scaledBorrowPrior = self.scaledTotalBorrows let scaledReservesPrior = self.scaledTotalReserves let scaledBorrowIndexPrior = self.scaledBorrowIndex // Get scaled borrow interest rate per block let scaledBorrowRatePerBlock = self.interestRateModelCap!.borrow()!.getBorrowRate(cash: scaledCashPrior, borrows: scaledBorrowPrior, reserves: scaledReservesPrior) let blockDelta = currentBlockNumber - accrualBlockNumberPrior let scaledInterestFactor = scaledBorrowRatePerBlock * blockDelta let scaleFactor = LendingConfig.scaleFactor let scaledInterestAccumulated = scaledInterestFactor * scaledBorrowPrior / scaleFactor let scaledTotalBorrowsNew = scaledInterestAccumulated + scaledBorrowPrior let scaledTotalReservesNew = self.scaledReserveFactor * scaledInterestAccumulated / scaleFactor + scaledReservesPrior let scaledBorrowIndexNew = scaledInterestFactor * scaledBorrowIndexPrior / scaleFactor + scaledBorrowIndexPrior return [ currentBlockNumber, scaledBorrowIndexNew, scaledTotalBorrowsNew, scaledTotalReservesNew ] } /// Accrue Interest /// /// Applies accrued interest to total borrows and reserves. /// pub fun accrueInterest() { // Return early if accrue 0 interest if (UInt256(getCurrentBlock().height) == self.accrualBlockNumber) { return } let scaledCashPrior = self.getPoolCash() let scaledBorrowPrior = self.scaledTotalBorrows let res = self.accrueInterestReadonly() // Write calculated values into contract storage self.accrualBlockNumber = res[0] self.scaledBorrowIndex = res[1] self.scaledTotalBorrows = res[2] self.scaledTotalReserves = res[3] emit AccrueInterest(scaledCashPrior, res[2]-scaledBorrowPrior, self.scaledBorrowIndex, self.scaledTotalBorrows) } /// Calculates the exchange rate from the underlying to virtual lpToken (i.e. how many UnderlyingToken per virtual lpToken) /// Note: It doesn't call accrueInterest() first to update with latest states which is used in calculating the exchange rate. /// pub fun underlyingToLpTokenRateSnapshotScaled(): UInt256 { if (self.scaledTotalSupply == 0) { return self.scaledInitialExchangeRate } else { return (self.getPoolCash() + self.scaledTotalBorrows - self.scaledTotalReserves) * LendingConfig.scaleFactor / self.scaledTotalSupply } } /// Calculates the scaled borrow balance of borrower address based on stored states /// Note: It doesn't call accrueInterest() first to update with latest states which is used in calculating the borrow balance. /// pub fun borrowBalanceSnapshotScaled(borrowerAddress: Address): UInt256 { if (self.accountBorrows.containsKey(borrowerAddress) == false) { return 0 } let borrower = self.accountBorrows[borrowerAddress]! return borrower.scaledPrincipal * self.scaledBorrowIndex / borrower.scaledInterestIndex } /// Supplier deposits underlying asset's Vault into the pool. /// /// @Param SupplierAddr - The address of the account which is supplying the assets /// @Param InUnderlyingVault - The vault for deposit and its type should match the pool's underlying token type /// /// Interest will be accrued up to the current block. /// The lending pool will mint the corresponding lptoken according to the current /// exchange rate of lptoken as the user's deposit certificate and save it in the contract. /// pub fun supply(supplierAddr: Address, inUnderlyingVault: @FungibleToken.Vault) { pre { inUnderlyingVault.balance > 0.0: LendingError.ErrorEncode(msg: "Supplied zero", err: LendingError.ErrorCode.EMPTY_FUNGIBLE_TOKEN_VAULT) inUnderlyingVault.isInstance(self.underlyingAssetType): LendingError.ErrorEncode( msg: "Supplied vault and pool underlying type mismatch", err: LendingError.ErrorCode.MISMATCHED_INPUT_VAULT_TYPE_WITH_POOL ) } // 1. Accrues interests and checkpoints latest states self.accrueInterest() // 2. Check whether or not supplyAllowed() let scaledAmount = LendingConfig.UFix64ToScaledUInt256(inUnderlyingVault.balance) let err = self.comptrollerCap!.borrow()!.supplyAllowed( poolCertificate: <- create PoolCertificate(), poolAddress: self.poolAddress, supplierAddress: supplierAddr, supplyUnderlyingAmountScaled: scaledAmount ) assert(err == nil, message: err ?? "") // 3. Deposit into underlying vault and mint corresponding PoolTokens let underlyingToken2LpTokenRateScaled = self.underlyingToLpTokenRateSnapshotScaled() let scaledMintVirtualAmount = scaledAmount * LendingConfig.scaleFactor / underlyingToken2LpTokenRateScaled // mint pool tokens for supply certificate self.accountLpTokens[supplierAddr] = scaledMintVirtualAmount + (self.accountLpTokens[supplierAddr] ?? 0) self.scaledTotalSupply = self.scaledTotalSupply + scaledMintVirtualAmount self.underlyingVault.deposit(from: <-inUnderlyingVault) emit Supply(supplier: supplierAddr, scaledSuppliedUnderlyingAmount: scaledAmount, scaledMintedLpTokenAmount: scaledMintVirtualAmount) } /// Redeems lpTokens for the underlying asset's vault /// or /// Redeems lpTokens for a specified amount of underlying asset /// /// @Param redeemer - The address of the account which is redeeming the tokens /// @Param numLpTokenToRedeem - The number of lpTokens to redeem into underlying (only one of numLpTokenToRedeem or numUnderlyingToRedeem may be non-zero) /// @Param numUnderlyingToRedeem - The amount of underlying to receive from redeeming lpTokens /// @Return The redeemed vault resource of pool's underlying token /// /// Since redeemer decreases his overall collateral ratio across all markets, safety check happenes inside comptroller. /// access(self) fun redeemInternal( redeemer: Address, numLpTokenToRedeem: UFix64, numUnderlyingToRedeem: UFix64 ): @FungibleToken.Vault { pre { numLpTokenToRedeem == 0.0 || numUnderlyingToRedeem == 0.0: LendingError.ErrorEncode( msg: "numLpTokenToRedeem or numUnderlyingToRedeem must be 0.0.", err: LendingError.ErrorCode.INVALID_PARAMETERS ) self.accountLpTokens.containsKey(redeemer): LendingError.ErrorEncode( msg: "redeemer has no supply to redeem from", err: LendingError.ErrorCode.REDEEM_FAILED_NO_ENOUGH_LP_TOKEN ) } // 1. Accrues interests and checkpoints latest states self.accrueInterest() // 2. Check whether or not redeemAllowed() var scaledLpTokenToRedeem: UInt256 = 0 var scaledUnderlyingToRedeem: UInt256 = 0 let scaledUnderlyingToLpRate = self.underlyingToLpTokenRateSnapshotScaled() let scaleFactor = LendingConfig.scaleFactor if (numLpTokenToRedeem == 0.0) { // redeem all // the special value of `UFIx64.max` indicating to redeem all virtual LP tokens the redeemer has if numUnderlyingToRedeem == UFix64.max { scaledLpTokenToRedeem = self.accountLpTokens[redeemer]! scaledUnderlyingToRedeem = scaledLpTokenToRedeem * scaledUnderlyingToLpRate / scaleFactor } else { scaledLpTokenToRedeem = LendingConfig.UFix64ToScaledUInt256(numUnderlyingToRedeem) * scaleFactor / scaledUnderlyingToLpRate scaledUnderlyingToRedeem = LendingConfig.UFix64ToScaledUInt256(numUnderlyingToRedeem) } } else { if numLpTokenToRedeem == UFix64.max { scaledLpTokenToRedeem = self.accountLpTokens[redeemer]! } else { scaledLpTokenToRedeem = LendingConfig.UFix64ToScaledUInt256(numLpTokenToRedeem) } scaledUnderlyingToRedeem = scaledLpTokenToRedeem * scaledUnderlyingToLpRate / scaleFactor } assert(scaledLpTokenToRedeem <= self.accountLpTokens[redeemer]!, message: LendingError.ErrorEncode( msg: "exceeded redeemer lp token balance", err: LendingError.ErrorCode.REDEEM_FAILED_NO_ENOUGH_LP_TOKEN ) ) let err = self.comptrollerCap!.borrow()!.redeemAllowed( poolCertificate: <- create PoolCertificate(), poolAddress: self.poolAddress, redeemerAddress: redeemer, redeemLpTokenAmountScaled: scaledLpTokenToRedeem, ) assert(err == nil, message: err ?? "") // 3. Burn virtual lpTokens, withdraw from underlying vault and return it assert(scaledUnderlyingToRedeem <= self.getPoolCash(), message: LendingError.ErrorEncode( msg: "insufficient pool liquidity to redeem", err: LendingError.ErrorCode.INSUFFICIENT_POOL_LIQUIDITY ) ) self.scaledTotalSupply = self.scaledTotalSupply - scaledLpTokenToRedeem if (self.accountLpTokens[redeemer] == scaledLpTokenToRedeem) { self.accountLpTokens.remove(key: redeemer) } else { self.accountLpTokens[redeemer] = self.accountLpTokens[redeemer]! - scaledLpTokenToRedeem } emit Redeem( redeemer: redeemer, scaledLpTokenToRedeem: scaledLpTokenToRedeem, scaledRedeemedUnderlyingAmount: scaledUnderlyingToRedeem ) let amountUnderlyingToRedeem = LendingConfig.ScaledUInt256ToUFix64(scaledUnderlyingToRedeem) return <- self.underlyingVault.withdraw(amount: amountUnderlyingToRedeem) } /// User redeems lpTokens for the underlying asset's vault /// /// @Param userCertificateCap - User identity certificate and it can provide a valid user address proof /// @Param numLpTokenToRedeem - The number of lpTokens to redeem into underlying /// @Return The redeemed vault resource of pool's underlying token /// /// @Notice It is more convenient to use a resource certificate on flow for authentication than signing a signature. /// /// RedeemerAddress is inferred from the private capability to the IdentityCertificate resource, /// which is stored in user account and can only be given by its owner. /// The special value of numLpTokenToRedeem `UFIx64.max` indicating to redeem all virtual LP tokens the redeemer has. /// pub fun redeem( userCertificateCap: Capability<&{LendingInterfaces.IdentityCertificate}>, numLpTokenToRedeem: UFix64 ): @FungibleToken.Vault { pre { numLpTokenToRedeem > 0.0: LendingError.ErrorEncode(msg: "Redeemed zero", err: LendingError.ErrorCode.INVALID_PARAMETERS) userCertificateCap.check() && userCertificateCap.borrow()!.owner != nil: LendingError.ErrorEncode( msg: "Cannot borrow reference to IdentityCertificate", err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE ) self.checkUserCertificateType(certCap: userCertificateCap): LendingError.ErrorEncode( msg: "Certificate not issued by system", err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE ) } let redeemerAddress = userCertificateCap.borrow()!.owner!.address let res <- self.redeemInternal( redeemer: redeemerAddress, numLpTokenToRedeem: numLpTokenToRedeem, numUnderlyingToRedeem: 0.0 ) return <- res } /// User redeems lpTokens for a specified amount of underlying asset /// /// @Param userCertificateCap - User identity certificate and it can provide a valid user address proof /// @Param numUnderlyingToRedeem - The amount of underlying to receive from redeeming lpTokens /// @Return The redeemed vault resource of pool's underlying token /// /// @Notice It is more convenient to use a resource certificate on flow for authentication than signing a signature. /// /// RedeemerAddress is inferred from the private capability to the IdentityCertificate resource, /// which is stored in user account and can only be given by its owner. /// The special value of numUnderlyingToRedeem `UFIx64.max` indicating to redeem all the underlying liquidity. /// pub fun redeemUnderlying( userCertificateCap: Capability<&{LendingInterfaces.IdentityCertificate}>, numUnderlyingToRedeem: UFix64 ): @FungibleToken.Vault { pre { numUnderlyingToRedeem > 0.0: LendingError.ErrorEncode(msg: "Redeemed zero", err: LendingError.ErrorCode.INVALID_PARAMETERS) userCertificateCap.check() && userCertificateCap.borrow()!.owner != nil: LendingError.ErrorEncode( msg: "Cannot borrow reference to IdentityCertificate", err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE ) self.checkUserCertificateType(certCap: userCertificateCap): LendingError.ErrorEncode( msg: "Certificate not issued by system", err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE ) } let redeemerAddress = userCertificateCap.borrow()!.owner!.address let res <- self.redeemInternal( redeemer: redeemerAddress, numLpTokenToRedeem: 0.0, numUnderlyingToRedeem: numUnderlyingToRedeem ) return <- res } /// User borrows underlying asset from the pool. /// /// @Param userCertificateCap - User identity certificate and it can provide a valid user address proof /// @Param borrowAmount - The amount of the underlying asset to borrow /// @Return The vault of borrow asset /// /// @Notice It is more convenient to use a resource certificate on flow for authentication than signing a signature. /// /// Since borrower would decrease his overall collateral ratio across all markets, safety check happenes inside comptroller /// pub fun borrow( userCertificateCap: Capability<&{LendingInterfaces.IdentityCertificate}>, borrowAmount: UFix64, ): @FungibleToken.Vault { pre { borrowAmount > 0.0: LendingError.ErrorEncode(msg: "borrowAmount zero", err: LendingError.ErrorCode.INVALID_PARAMETERS) userCertificateCap.check() && userCertificateCap.borrow()!.owner != nil: LendingError.ErrorEncode( msg: "Cannot borrow reference to IdentityCertificate", err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE ) self.checkUserCertificateType(certCap: userCertificateCap): LendingError.ErrorEncode( msg: "Certificate not issued by system", err: LendingError.ErrorCode.INVALID_USER_CERTIFICATE ) } // 1. Accrues interests and checkpoints latest states self.accrueInterest() // 2. Pool liquidity check let scaledBorrowAmount = LendingConfig.UFix64ToScaledUInt256(borrowAmount) assert(scaledBorrowAmount <= self.getPoolCash(), message: LendingError.ErrorEncode( msg: "insufficient pool liquidity to borrow", err: LendingError.ErrorCode.INSUFFICIENT_POOL_LIQUIDITY ) ) // 3. Check whether or not borrowAllowed() let borrower = userCertificateCap.borrow()!.owner!.address let err = self.comptrollerCap!.borrow()!.borrowAllowed( poolCertificate: <- create PoolCertificate(), poolAddress: self.poolAddress, borrowerAddress: borrower, borrowUnderlyingAmountScaled: scaledBorrowAmount ) assert(err == nil, message: err ?? "") // 4. Updates borrow states, withdraw from pool underlying vault and deposits into borrower's account self.scaledTotalBorrows = self.scaledTotalBorrows + scaledBorrowAmount let scaledBorrowBalanceNew = scaledBorrowAmount + self.borrowBalanceSnapshotScaled(borrowerAddress: borrower) self.accountBorrows[borrower] = BorrowSnapshot(principal: scaledBorrowBalanceNew, interestIndex: self.scaledBorrowIndex) emit Borrow(borrower: borrower, scaledBorrowAmount: scaledBorrowAmount, scaledBorrowerTotalBorrows: scaledBorrowBalanceNew, scaledPoolTotalBorrows: self.scaledTotalBorrows) let res <- self.underlyingVault.withdraw(amount: borrowAmount) return <- res } /// Repay the borrower's borrow /// /// @Param borrower - The address of the borrower /// @Param borrowAmount - The amount to repay /// @Return The overpaid vault will be returned. /// /// @Note: Caller ensures that LendingPool.accrueInterest() has been called with latest states checkpointed /// access(self) fun repayBorrowInternal(borrower: Address, repayUnderlyingVault: @FungibleToken.Vault): @FungibleToken.Vault? { // Check whether or not repayAllowed() let scaledRepayAmount = LendingConfig.UFix64ToScaledUInt256(repayUnderlyingVault.balance) let scaledAccountTotalBorrows = self.borrowBalanceSnapshotScaled(borrowerAddress: borrower) let scaledActualRepayAmount = scaledAccountTotalBorrows > scaledRepayAmount ? scaledRepayAmount : scaledAccountTotalBorrows let err = self.comptrollerCap!.borrow()!.repayAllowed( poolCertificate: <- create PoolCertificate(), poolAddress: self.poolAddress, borrowerAddress: borrower, repayUnderlyingAmountScaled: scaledActualRepayAmount ) assert(err == nil, message: err ?? "") // Updates borrow states, deposit repay Vault into pool underlying vault and return any remaining Vault let scaledAccountTotalBorrowsNew = scaledAccountTotalBorrows > scaledRepayAmount ? scaledAccountTotalBorrows - scaledRepayAmount : (0 as UInt256) self.scaledTotalBorrows = self.scaledTotalBorrows - scaledActualRepayAmount emit Repay(borrower: borrower, scaledActualRepayAmount: scaledActualRepayAmount, scaledBorrowerTotalBorrows: scaledAccountTotalBorrowsNew, scaledPoolTotalBorrows: self.scaledTotalBorrows) if (scaledAccountTotalBorrows > scaledRepayAmount) { self.accountBorrows[borrower] = BorrowSnapshot(principal: scaledAccountTotalBorrowsNew, interestIndex: self.scaledBorrowIndex) self.underlyingVault.deposit(from: <-repayUnderlyingVault) return nil } else { self.accountBorrows.remove(key: borrower) let surplusAmount = LendingConfig.ScaledUInt256ToUFix64(scaledRepayAmount - scaledAccountTotalBorrows) self.underlyingVault.deposit(from: <-repayUnderlyingVault) return <- self.underlyingVault.withdraw(amount: surplusAmount) } } /// Anyone can repay borrow with a underlying Vault and receives a new underlying Vault if there's still any remaining left. /// /// @Param borrower - The address of the borrower /// @Param borrowAmount - The amount to repay /// @Return The overpaid vault will be returned. /// /// @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), /// this is allowed as there's no safety issue to do so. /// pub fun repayBorrow(borrower: Address, repayUnderlyingVault: @FungibleToken.Vault): @FungibleToken.Vault? { pre { repayUnderlyingVault.balance > 0.0: LendingError.ErrorEncode(msg: "Repaid zero", err: LendingError.ErrorCode.EMPTY_FUNGIBLE_TOKEN_VAULT) repayUnderlyingVault.isInstance(self.underlyingAssetType): LendingError.ErrorEncode( msg: "Repaid vault and pool underlying type mismatch", err: LendingError.ErrorCode.MISMATCHED_INPUT_VAULT_TYPE_WITH_POOL ) } // Accrues interests and checkpoints latest states self.accrueInterest() let res <- self.repayBorrowInternal(borrower: borrower, repayUnderlyingVault: <-repayUnderlyingVault) return <- res } /// Liquidates the borrowers collateral. /// /// @Param liquidator - The address of the liquidator who will receive the collateral lpToken transfer /// @Param borrower - The borrower to be liquidated /// @Param poolCollateralizedToSeize - The market address in which to seize collateral from the borrower /// @Param repayUnderlyingVault - The amount of the underlying borrowed asset in this pool to repay /// @Return The overLiquidate vault will be returned. /// /// The collateral lpTokens seized is transferred to the liquidator. /// pub fun liquidate( liquidator: Address, borrower: Address, poolCollateralizedToSeize: Address, repayUnderlyingVault: @FungibleToken.Vault ): @FungibleToken.Vault? { pre { repayUnderlyingVault.balance > 0.0: LendingError.ErrorEncode(msg: "Liquidator repaid zero", err: LendingError.ErrorCode.EMPTY_FUNGIBLE_TOKEN_VAULT) repayUnderlyingVault.isInstance(self.underlyingAssetType): LendingError.ErrorEncode( msg: "Liquidator repaid vault and pool underlying type mismatch", err: LendingError.ErrorCode.MISMATCHED_INPUT_VAULT_TYPE_WITH_POOL ) } // 1. Accrues interests and checkpoints latest states self.accrueInterest() // 2. Check whether or not liquidateAllowed() let scaledUnderlyingAmountToRepay = LendingConfig.UFix64ToScaledUInt256(repayUnderlyingVault.balance) let err = self.comptrollerCap!.borrow()!.liquidateAllowed( poolCertificate: <- create PoolCertificate(), poolBorrowed: self.poolAddress, poolCollateralized: poolCollateralizedToSeize, borrower: borrower, repayUnderlyingAmountScaled: scaledUnderlyingAmountToRepay ) assert(err == nil, message: err ?? "") // 3. Liquidator repays on behave of borrower assert(liquidator != borrower, message: LendingError.ErrorEncode( msg: "liquidator and borrower cannot be the same", err: LendingError.ErrorCode.SAME_LIQUIDATOR_AND_BORROWER ) ) let remainingVault <- self.repayBorrowInternal(borrower: borrower, repayUnderlyingVault: <-repayUnderlyingVault) let scaledRemainingAmount = LendingConfig.UFix64ToScaledUInt256(remainingVault?.balance ?? 0.0) let scaledActualRepayAmount = scaledUnderlyingAmountToRepay - scaledRemainingAmount // Calculate collateralLpTokenSeizedAmount based on actualRepayAmount let scaledCollateralLpTokenSeizedAmount = self.comptrollerCap!.borrow()!.calculateCollateralPoolLpTokenToSeize( borrower: borrower, borrowPool: self.poolAddress, collateralPool: poolCollateralizedToSeize, actualRepaidBorrowAmountScaled: scaledActualRepayAmount ) // 4. seizeInternal if current pool is also borrower's collateralPool; otherwise seize external collateralPool if (poolCollateralizedToSeize == self.poolAddress) { self.seizeInternal( liquidator: liquidator, borrower: borrower, scaledBorrowerLpTokenToSeize: scaledCollateralLpTokenSeizedAmount ) } else { // Seize external let externalPoolPublicRef = getAccount(poolCollateralizedToSeize) .getCapability<&{LendingInterfaces.PoolPublic}>(LendingConfig.PoolPublicPublicPath).borrow() ?? panic( LendingError.ErrorEncode( msg: "Cannot borrow reference to external PoolPublic resource", err: LendingError.ErrorCode.CANNOT_ACCESS_POOL_PUBLIC_CAPABILITY ) ) externalPoolPublicRef.seize( seizerPoolCertificate: <- create PoolCertificate(), seizerPool: self.poolAddress, liquidator: liquidator, borrower: borrower, scaledBorrowerCollateralLpTokenToSeize: scaledCollateralLpTokenSeizedAmount ) } emit Liquidate( liquidator: liquidator, borrower: borrower, scaledActualRepaidUnderlying: scaledActualRepayAmount, collateralPoolToSeize: poolCollateralizedToSeize, scaledCollateralPoolLpTokenSeized: scaledCollateralLpTokenSeizedAmount ) return <-remainingVault } /// External seize, transfers collateral tokens (this market) to the liquidator. /// /// @Param seizerPoolCertificate - Pool's certificate guarantee that this interface can only be called by other valid markets /// @Param seizerPool - The external pool seizing the current collateral pool (i.e. borrowPool) /// @Param liquidator - The address of the liquidator who will receive the collateral lpToken transfer /// @Param borrower - The borrower to be liquidated /// @Param scaledBorrowerCollateralLpTokenToSeize - The amount of collateral lpTokens that will be seized from borrower to liquidator /// /// Only used for "external" seize. Run-time type check of pool certificate ensures it can only be called by other supported markets. /// pub fun seize( seizerPoolCertificate: @{LendingInterfaces.IdentityCertificate}, seizerPool: Address, liquidator: Address, borrower: Address, scaledBorrowerCollateralLpTokenToSeize: UInt256 ) { pre { seizerPool != self.poolAddress: LendingError.ErrorEncode( msg: "External seize only, seizerPool cannot be current pool", err: LendingError.ErrorCode.EXTERNAL_SEIZE_FROM_SELF ) } // 1. Check and verify caller from another LendingPool contract let err = self.comptrollerCap!.borrow()!.callerAllowed( callerCertificate: <- seizerPoolCertificate, callerAddress: seizerPool ) assert(err == nil, message: err ?? "") // 2. Accrues interests and checkpoints latest states self.accrueInterest() // 3. seizeInternal self.seizeInternal( liquidator: liquidator, borrower: borrower, scaledBorrowerLpTokenToSeize: scaledBorrowerCollateralLpTokenToSeize ) } /// Internal seize /// /// @Param liquidator - The address of the liquidator who will receive the collateral lpToken transfer /// @Param borrower - The borrower to be liquidated /// @Param scaledBorrowerLpTokenToSeize - The amount of collateral lpTokens that will be seized from borrower to liquidator /// /// Caller ensures accrueInterest() has been called /// access(self) fun seizeInternal( liquidator: Address, borrower: Address, scaledBorrowerLpTokenToSeize: UInt256 ) { pre { liquidator != borrower: LendingError.ErrorEncode( msg: "seize: liquidator == borrower", err: LendingError.ErrorCode.SAME_LIQUIDATOR_AND_BORROWER ) } let err = self.comptrollerCap!.borrow()!.seizeAllowed( poolCertificate: <- create PoolCertificate(), borrowPool: self.poolAddress, collateralPool: self.poolAddress, liquidator: liquidator, borrower: borrower, seizeCollateralPoolLpTokenAmountScaled: scaledBorrowerLpTokenToSeize ) assert(err == nil, message: err ?? "") let scaleFactor = LendingConfig.scaleFactor let scaledProtocolSeizedLpTokens = scaledBorrowerLpTokenToSeize * self.scaledPoolSeizeShare / scaleFactor let scaledLiquidatorSeizedLpTokens = scaledBorrowerLpTokenToSeize - scaledProtocolSeizedLpTokens let scaledUnderlyingToLpTokenRate = self.underlyingToLpTokenRateSnapshotScaled() let scaledAddedUnderlyingReserves = scaledUnderlyingToLpTokenRate * scaledProtocolSeizedLpTokens / scaleFactor self.scaledTotalReserves = self.scaledTotalReserves + scaledAddedUnderlyingReserves self.scaledTotalSupply = self.scaledTotalSupply - scaledProtocolSeizedLpTokens // in-place liquidation: only virtual lpToken records get updated, no token deposit / withdraw needs to happen if (self.accountLpTokens[borrower] == scaledBorrowerLpTokenToSeize) { self.accountLpTokens.remove(key: borrower) } else { self.accountLpTokens[borrower] = self.accountLpTokens[borrower]! - scaledBorrowerLpTokenToSeize } self.accountLpTokens[liquidator] = scaledLiquidatorSeizedLpTokens + (self.accountLpTokens[liquidator] ?? 0) emit ReservesAdded(donator: self.poolAddress, scaledAddedUnderlyingAmount: scaledAddedUnderlyingReserves, scaledNewTotalReserves: self.scaledTotalReserves) } /// 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: /// 1. executor implements FlashLoanExecutor resource interface and sets up corresponding resource to receive & process requested tokens, and /// 2. executor repays back requested amount + fees (dominated by 'flashloanRateBps x amount'), and /// all in one atomic function call. /// @params: User-definited extra data passed to executor for further auth/check/decode /// pub fun flashloan(executorCap: Capability<&{LendingInterfaces.FlashLoanExecutor}>, requestedAmount: UFix64, params: {String: AnyStruct}) { pre { self.isFlashloanOpen(): LendingError.ErrorEncode( msg: "LendingError: flashloan is not open", err: LendingError.ErrorCode.MARKET_NOT_OPEN ) requestedAmount > 0.0 && requestedAmount < self.underlyingVault.balance: LendingError.ErrorEncode( msg: "LendingError: flashloan invalid requested amount", err: LendingError.ErrorCode.INSUFFICIENT_POOL_LIQUIDITY ) executorCap.check(): LendingError.ErrorEncode( msg: "LendingError: flashloan executor resource not properly setup", err: LendingError.ErrorCode.FLASHLOAN_EXECUTOR_SETUP ) } // Accrues interests and checkpoints latest states self.accrueInterest() let requestedAmountScaled = LendingConfig.UFix64ToScaledUInt256(requestedAmount) let tokenOut <-self.underlyingVault.withdraw(amount: requestedAmount) self.scaledTotalBorrows = self.scaledTotalBorrows + requestedAmountScaled let tokenIn <- executorCap.borrow()!.executeAndRepay(loanedToken: <- tokenOut, params: params) assert(tokenIn.isInstance(self.underlyingAssetType), message: LendingError.ErrorEncode( msg: "LendingError: flashloan repaid incompatible token", err: LendingError.ErrorCode.INVALID_PARAMETERS ) ) assert(tokenIn.balance >= requestedAmount * (1.0 + UFix64(self.getFlashloanRateBps()) / 10000.0), message: LendingError.ErrorEncode( msg: "LendingError: flashloan insufficient repayment", err: LendingError.ErrorCode.INVALID_PARAMETERS ) ) self.underlyingVault.deposit(from: <- tokenIn) self.scaledTotalBorrows = self.scaledTotalBorrows - requestedAmountScaled emit Flashloan(executor: executorCap.borrow()!.owner!.address, executorType: executorCap.borrow()!.getType(), originator: self.account.address, amount: requestedAmount) } /// Check whether or not the given certificate is issued by system /// access(self) fun checkUserCertificateType(certCap: Capability<&{LendingInterfaces.IdentityCertificate}>): Bool { return certCap.borrow()!.isInstance(self.comptrollerCap!.borrow()!.getUserCertificateType()) } pub fun getFlashloanRateBps(): UInt64 { return (self._reservedFields["flashloanRateBps"] as! UInt64?) ?? 5 } pub fun isFlashloanOpen(): Bool { return (self._reservedFields["isFlashloanOpen"] as! Bool?) ?? false } /// PoolCertificate /// /// Inherited from IdentityCertificate. /// Proof of identity for the pool. /// pub resource PoolCertificate: LendingInterfaces.IdentityCertificate {} /// PoolPublic /// /// The external interfaces of the pool, and will be exposed as a public capability. /// pub resource PoolPublic: LendingInterfaces.PoolPublic { pub fun getPoolAddress(): Address { return LendingPool.poolAddress } pub fun getUnderlyingTypeString(): String { let underlyingType = LendingPool.getUnderlyingAssetType() // "A.1654653399040a61.FlowToken.Vault" => "FlowToken" return underlyingType.slice(from: 19, upTo: underlyingType.length - 6) } pub fun getUnderlyingAssetType(): String { return LendingPool.getUnderlyingAssetType() } pub fun getUnderlyingToLpTokenRateScaled(): UInt256 { return LendingPool.underlyingToLpTokenRateSnapshotScaled() } pub fun getAccountLpTokenBalanceScaled(account: Address): UInt256 { return LendingPool.accountLpTokens[account] ?? 0 } pub fun getAccountBorrowBalanceScaled(account: Address): UInt256 { return LendingPool.borrowBalanceSnapshotScaled(borrowerAddress: account) } pub fun getAccountBorrowPrincipalSnapshotScaled(account: Address): UInt256 { if (LendingPool.accountBorrows.containsKey(account) == false) { return 0 } else { return LendingPool.accountBorrows[account]!.scaledPrincipal } } pub fun getAccountBorrowIndexSnapshotScaled(account: Address): UInt256 { if (LendingPool.accountBorrows.containsKey(account) == false) { return 0 } else { return LendingPool.accountBorrows[account]!.scaledInterestIndex } } pub fun getAccountSnapshotScaled(account: Address): [UInt256; 5] { return [ self.getUnderlyingToLpTokenRateScaled(), self.getAccountLpTokenBalanceScaled(account: account), self.getAccountBorrowBalanceScaled(account: account), self.getAccountBorrowPrincipalSnapshotScaled(account: account), self.getAccountBorrowIndexSnapshotScaled(account: account) ] } pub fun getAccountRealtimeScaled(account: Address): [UInt256; 5] { let accrueInterestRealtimeRes = self.accrueInterestReadonly() let poolBorrowIndexRealtime = accrueInterestRealtimeRes[1] let poolTotalBorrowRealtime = accrueInterestRealtimeRes[2] let poolTotalReserveRealtime = accrueInterestRealtimeRes[3] let underlyingTolpTokenRateRealtime = (LendingPool.getPoolCash() + poolTotalBorrowRealtime - poolTotalReserveRealtime) * LendingConfig.scaleFactor / LendingPool.scaledTotalSupply var borrowBalanceRealtimeScaled:UInt256 = 0 if (LendingPool.accountBorrows.containsKey(account)) { borrowBalanceRealtimeScaled = self.getAccountBorrowPrincipalSnapshotScaled(account: account) * poolBorrowIndexRealtime / self.getAccountBorrowIndexSnapshotScaled(account: account) } return [ underlyingTolpTokenRateRealtime, self.getAccountLpTokenBalanceScaled(account: account), borrowBalanceRealtimeScaled, self.getAccountBorrowPrincipalSnapshotScaled(account: account), self.getAccountBorrowIndexSnapshotScaled(account: account) ] } pub fun getPoolReserveFactorScaled(): UInt256 { return LendingPool.scaledReserveFactor } pub fun getInterestRateModelAddress(): Address { return LendingPool.interestRateModelAddress! } pub fun getPoolTotalBorrowsScaled(): UInt256 { return LendingPool.scaledTotalBorrows } pub fun getPoolAccrualBlockNumber(): UInt256 { return LendingPool.accrualBlockNumber } pub fun getPoolBorrowIndexScaled(): UInt256 { return LendingPool.scaledBorrowIndex } pub fun getPoolTotalLpTokenSupplyScaled(): UInt256 { return LendingPool.scaledTotalSupply } pub fun getPoolTotalSupplyScaled(): UInt256 { return LendingPool.getPoolCash() + LendingPool.scaledTotalBorrows } pub fun getPoolTotalReservesScaled(): UInt256 { return LendingPool.scaledTotalReserves } pub fun getPoolCash(): UInt256 { return LendingPool.getPoolCash() } pub fun getPoolSupplierCount(): UInt256 { return UInt256(LendingPool.accountLpTokens.length) } pub fun getPoolBorrowerCount(): UInt256 { return UInt256(LendingPool.accountBorrows.length) } pub fun getPoolSupplierList(): [Address] { return LendingPool.accountLpTokens.keys } pub fun getPoolSupplierSlicedList(from: UInt64, to: UInt64): [Address] { pre { from <= to && to < UInt64(LendingPool.accountLpTokens.length): LendingError.ErrorEncode( msg: "Index out of range", err: LendingError.ErrorCode.INVALID_PARAMETERS ) } let borrowers: &[Address] = &LendingPool.accountLpTokens.keys as &[Address] let list: [Address] = [] var i = from while i <= to { list.append(borrowers[i]) i = i + 1 } return list } pub fun getPoolBorrowerList(): [Address] { return LendingPool.accountBorrows.keys } pub fun getPoolBorrowerSlicedList(from: UInt64, to: UInt64): [Address] { pre { from <= to && to < UInt64(LendingPool.accountBorrows.length): LendingError.ErrorEncode( msg: "Index out of range", err: LendingError.ErrorCode.INVALID_PARAMETERS ) } let borrowers: &[Address] = &LendingPool.accountBorrows.keys as &[Address] let list: [Address] = [] var i = from while i <= to { list.append(borrowers[i]) i = i + 1 } return list } pub fun getPoolBorrowRateScaled(): UInt256 { return LendingPool.interestRateModelCap!.borrow()!.getBorrowRate( cash: LendingPool.getPoolCash(), borrows: LendingPool.scaledTotalBorrows, reserves: LendingPool.scaledTotalReserves ) } pub fun getPoolBorrowAprScaled(): UInt256 { let scaledBorrowRatePerBlock = LendingPool.interestRateModelCap!.borrow()!.getBorrowRate( cash: LendingPool.getPoolCash(), borrows: LendingPool.scaledTotalBorrows, reserves: LendingPool.scaledTotalReserves ) let blocksPerYear = LendingPool.interestRateModelCap!.borrow()!.getBlocksPerYear() return scaledBorrowRatePerBlock * blocksPerYear } pub fun getPoolSupplyAprScaled(): UInt256 { let scaledSupplyRatePerBlock = LendingPool.interestRateModelCap!.borrow()!.getSupplyRate( cash: LendingPool.getPoolCash(), borrows: LendingPool.scaledTotalBorrows, reserves: LendingPool.scaledTotalReserves, reserveFactor: LendingPool.scaledReserveFactor ) let blocksPerYear = LendingPool.interestRateModelCap!.borrow()!.getBlocksPerYear() return scaledSupplyRatePerBlock * blocksPerYear } /// The default flashloan rate is 5 bps (0.05%) pub fun getFlashloanRateBps(): UInt64 { return LendingPool.getFlashloanRateBps() } pub fun accrueInterest() { LendingPool.accrueInterest() } pub fun accrueInterestReadonly(): [UInt256; 4] { return LendingPool.accrueInterestReadonly() } pub fun getPoolCertificateType(): Type { return Type<@LendingPool.PoolCertificate>() } pub fun seize( seizerPoolCertificate: @{LendingInterfaces.IdentityCertificate}, seizerPool: Address, liquidator: Address, borrower: Address, scaledBorrowerCollateralLpTokenToSeize: UInt256 ) { LendingPool.seize( seizerPoolCertificate: <- seizerPoolCertificate, seizerPool: seizerPool, liquidator: liquidator, borrower: borrower, scaledBorrowerCollateralLpTokenToSeize: scaledBorrowerCollateralLpTokenToSeize ) } pub fun supply(supplierAddr: Address, inUnderlyingVault: @FungibleToken.Vault) { LendingPool.supply(supplierAddr: supplierAddr, inUnderlyingVault: <-inUnderlyingVault) } pub fun redeem(userCertificateCap: Capability<&{LendingInterfaces.IdentityCertificate}>, numLpTokenToRedeem: UFix64): @FungibleToken.Vault { return <-LendingPool.redeem(userCertificateCap: userCertificateCap, numLpTokenToRedeem: numLpTokenToRedeem) } pub fun redeemUnderlying(userCertificateCap: Capability<&{LendingInterfaces.IdentityCertificate}>, numUnderlyingToRedeem: UFix64): @FungibleToken.Vault { return <-LendingPool.redeemUnderlying(userCertificateCap: userCertificateCap, numUnderlyingToRedeem: numUnderlyingToRedeem) } pub fun borrow(userCertificateCap: Capability<&{LendingInterfaces.IdentityCertificate}>, borrowAmount: UFix64): @FungibleToken.Vault { return <-LendingPool.borrow(userCertificateCap: userCertificateCap, borrowAmount: borrowAmount) } pub fun repayBorrow(borrower: Address, repayUnderlyingVault: @FungibleToken.Vault): @FungibleToken.Vault? { return <-LendingPool.repayBorrow(borrower: borrower, repayUnderlyingVault: <-repayUnderlyingVault) } pub fun liquidate(liquidator: Address, borrower: Address, poolCollateralizedToSeize: Address, repayUnderlyingVault: @FungibleToken.Vault): @FungibleToken.Vault? { return <-LendingPool.liquidate(liquidator: liquidator, borrower: borrower, poolCollateralizedToSeize: poolCollateralizedToSeize, repayUnderlyingVault: <-repayUnderlyingVault) } pub fun flashloan(executorCap: Capability<&{LendingInterfaces.FlashLoanExecutor}>, requestedAmount: UFix64, params: {String: AnyStruct}) { LendingPool.flashloan(executorCap: executorCap, requestedAmount: requestedAmount, params: params) } } /// PoolAdmin /// pub resource PoolAdmin: LendingInterfaces.PoolAdminPublic { /// Admin function to call accrueInterest() to checkpoint latest states, and then update the interest rate model pub fun setInterestRateModel(newInterestRateModelAddress: Address) { post { LendingPool.interestRateModelCap != nil && LendingPool.interestRateModelCap!.check() == true: LendingError.ErrorEncode( msg: "Invalid contract address of the new interest rate", err: LendingError.ErrorCode.CANNOT_ACCESS_INTEREST_RATE_MODEL_CAPABILITY ) } LendingPool.accrueInterest() if (newInterestRateModelAddress != LendingPool.interestRateModelAddress) { let oldInterestRateModelAddress = LendingPool.interestRateModelAddress LendingPool.interestRateModelAddress = newInterestRateModelAddress LendingPool.interestRateModelCap = getAccount(newInterestRateModelAddress) .getCapability<&{LendingInterfaces.InterestRateModelPublic}>(LendingConfig.InterestRateModelPublicPath) emit NewInterestRateModel(oldInterestRateModelAddress, newInterestRateModelAddress) } return } /// Admin function to call accrueInterest() to checkpoint latest states, and then update reserveFactor pub fun setReserveFactor(newReserveFactor: UFix64) { pre { newReserveFactor <= 1.0: LendingError.ErrorEncode( msg: "Reserve factor out of range 1.0", err: LendingError.ErrorCode.INVALID_PARAMETERS ) } LendingPool.accrueInterest() let oldReserveFactor = LendingConfig.ScaledUInt256ToUFix64(LendingPool.scaledReserveFactor) LendingPool.scaledReserveFactor = LendingConfig.UFix64ToScaledUInt256(newReserveFactor) emit NewReserveFactor(oldReserveFactor, newReserveFactor) return } /// Admin function to update poolSeizeShare pub fun setPoolSeizeShare(newPoolSeizeShare: UFix64) { pre { newPoolSeizeShare <= 1.0: LendingError.ErrorEncode( msg: "Pool seize share factor out of range 1.0", err: LendingError.ErrorCode.INVALID_PARAMETERS ) } let oldPoolSeizeShare = LendingConfig.ScaledUInt256ToUFix64(LendingPool.scaledPoolSeizeShare) LendingPool.scaledPoolSeizeShare = LendingConfig.UFix64ToScaledUInt256(newPoolSeizeShare) emit NewPoolSeizeShare(oldPoolSeizeShare, newPoolSeizeShare) return } /// Admin function to set comptroller pub fun setComptroller(newComptrollerAddress: Address) { post { LendingPool.comptrollerCap != nil && LendingPool.comptrollerCap!.check() == true: LendingError.ErrorEncode( msg: "Cannot borrow reference to ComptrollerPublic resource", err: LendingError.ErrorCode.CANNOT_ACCESS_COMPTROLLER_PUBLIC_CAPABILITY ) } if (newComptrollerAddress != LendingPool.comptrollerAddress) { let oldComptrollerAddress = LendingPool.comptrollerAddress LendingPool.comptrollerAddress = newComptrollerAddress LendingPool.comptrollerCap = getAccount(newComptrollerAddress) .getCapability<&{LendingInterfaces.ComptrollerPublic}>(LendingConfig.ComptrollerPublicPath) emit NewComptroller(oldComptrollerAddress, newComptrollerAddress) } } /// Admin function to initialize pool. /// Note: can be called only once pub fun initializePool( reserveFactor: UFix64, poolSeizeShare: UFix64, interestRateModelAddress: Address ) { pre { LendingPool.accrualBlockNumber == 0 && LendingPool.scaledBorrowIndex == 0: LendingError.ErrorEncode( msg: "Pool can only be initialized once", err: LendingError.ErrorCode.POOL_INITIALIZED ) reserveFactor <= 1.0 && poolSeizeShare <= 1.0: LendingError.ErrorEncode( msg: "ReserveFactor | poolSeizeShare out of range 1.0", err: LendingError.ErrorCode.INVALID_PARAMETERS ) } post { LendingPool.interestRateModelCap != nil && LendingPool.interestRateModelCap!.check() == true: LendingError.ErrorEncode( msg: "InterestRateModel not properly initialized", err: LendingError.ErrorCode.CANNOT_ACCESS_INTEREST_RATE_MODEL_CAPABILITY ) } LendingPool.accrualBlockNumber = UInt256(getCurrentBlock().height) LendingPool.scaledBorrowIndex = LendingConfig.scaleFactor LendingPool.scaledReserveFactor = LendingConfig.UFix64ToScaledUInt256(reserveFactor) LendingPool.scaledPoolSeizeShare = LendingConfig.UFix64ToScaledUInt256(poolSeizeShare) LendingPool.interestRateModelAddress = interestRateModelAddress LendingPool.interestRateModelCap = getAccount(interestRateModelAddress) .getCapability<&{LendingInterfaces.InterestRateModelPublic}>(LendingConfig.InterestRateModelPublicPath) } /// Admin function to withdraw pool reserve pub fun withdrawReserves(reduceAmount: UFix64): @FungibleToken.Vault { LendingPool.accrueInterest() let reduceAmountScaled = reduceAmount == UFix64.max ? LendingPool.scaledTotalReserves : LendingConfig.UFix64ToScaledUInt256(reduceAmount) assert(reduceAmountScaled <= LendingPool.scaledTotalReserves, message: LendingError.ErrorEncode( msg: "exceeded pool total reserve", err: LendingError.ErrorCode.EXCEED_TOTAL_RESERVES ) ) assert(reduceAmountScaled <= LendingPool.getPoolCash(), message: LendingError.ErrorEncode( msg: "insufficient pool liquidity to withdraw reserve", err: LendingError.ErrorCode.INSUFFICIENT_POOL_LIQUIDITY ) ) LendingPool.scaledTotalReserves = LendingPool.scaledTotalReserves - reduceAmountScaled emit ReservesReduced(scaledReduceAmount: reduceAmountScaled, scaledNewTotalReserves: LendingPool.scaledTotalReserves) return <- LendingPool.underlyingVault.withdraw(amount: LendingConfig.ScaledUInt256ToUFix64(reduceAmountScaled)) } pub fun setFlashloanRateBps(rateBps: UInt64) { pre { rateBps >= 0 && rateBps <= 10000: LendingError.ErrorEncode( msg: "LendingPool: flashloan rateBps should be in [0, 10000]", err: LendingError.ErrorCode.INVALID_PARAMETERS ) } emit FlashloanRateChanged(oldRateBps: LendingPool.getFlashloanRateBps(), newRateBps: rateBps) LendingPool._reservedFields["flashloanRateBps"] = rateBps } pub fun setFlashloanOpen(isOpen: Bool) { emit FlashloanOpen(isOpen: isOpen) LendingPool._reservedFields["isFlashloanOpen"] = isOpen } } init() { self.PoolAdminStoragePath = /storage/incrementLendingPoolAdmin self.UnderlyingAssetVaultStoragePath = /storage/poolUnderlyingAssetVault self.PoolPublicStoragePath = /storage/incrementLendingPoolPublic self.PoolPublicPublicPath = /public/incrementLendingPoolPublic self.poolAddress = self.account.address self.scaledInitialExchangeRate = LendingConfig.scaleFactor self.accrualBlockNumber = 0 self.scaledBorrowIndex = 0 self.scaledTotalBorrows = 0 self.scaledTotalReserves = 0 self.scaledReserveFactor = 0 self.scaledPoolSeizeShare = 0 self.scaledTotalSupply = 0 self.accountLpTokens = {} self.accountBorrows = {} self.interestRateModelAddress = nil self.interestRateModelCap = nil self.comptrollerAddress = nil self.comptrollerCap = nil self._reservedFields = {} self.underlyingVault <- self.account.load<@FungibleToken.Vault>(from: self.UnderlyingAssetVaultStoragePath) ?? panic("Deployer should own zero-balanced underlying asset vault first") self.underlyingAssetType = self.underlyingVault.getType() assert(self.underlyingVault.balance == 0.0, message: "Must initialize pool with zero-balanced underlying asset vault") // save pool admin destroy <-self.account.load<@AnyResource>(from: self.PoolAdminStoragePath) self.account.save(<-create PoolAdmin(), to: self.PoolAdminStoragePath) // save pool public interface self.account.unlink(self.PoolPublicPublicPath) destroy <-self.account.load<@AnyResource>(from: self.PoolPublicStoragePath) self.account.save(<-create PoolPublic(), to: self.PoolPublicStoragePath) self.account.unlink(self.PoolPublicPublicPath) self.account.link<&{LendingInterfaces.PoolPublic}>(LendingConfig.PoolPublicPublicPath, target: self.PoolPublicStoragePath) } }

Cadence Script

1transaction(name: String, code: String ) {
2		prepare(signer: AuthAccount) {
3			signer.contracts.add(name: name, code: code.utf8 )
4		}
5	}