diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index 62133be4..4641cbf4 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -312,7 +312,7 @@ access(all) contract FlowALPv0 { if let tokenState = self.state.getTokenState(tokenType) { return tokenState.getInsuranceRate() } - + return nil } @@ -501,7 +501,7 @@ access(all) contract FlowALPv0 { post { !self.state.isPositionLocked(pid): "Position is not unlocked" } - + self.lockPosition(pid) let positionView = self.buildPositionView(pid: pid) @@ -521,7 +521,7 @@ access(all) contract FlowALPv0 { let Pc_oracle = self.config.getPriceOracle().price(ofToken: seizeType)! // collateral price given by oracle ($/C) // Price of collateral, denominated in debt token, implied by oracle (D/C) // Oracle says: "1 unit of collateral is worth `Pcd_oracle` units of debt" - let Pcd_oracle = Pc_oracle / Pd_oracle + let Pcd_oracle = Pc_oracle / Pd_oracle // Compute the health factor which would result if we were to accept this liquidation let Ce_pre = balanceSheet.effectiveCollateral // effective collateral pre-liquidation @@ -532,7 +532,7 @@ access(all) contract FlowALPv0 { // Ce_seize = effective value of seized collateral ($) let Ce_seize = FlowALPMath.effectiveCollateral(credit: UFix128(seizeAmount), price: UFix128(Pc_oracle), collateralFactor: Fc) // De_seize = effective value of repaid debt ($) - let De_seize = FlowALPMath.effectiveDebt(debit: UFix128(repayAmount), price: UFix128(Pd_oracle), borrowFactor: Fd) + let De_seize = FlowALPMath.effectiveDebt(debit: UFix128(repayAmount), price: UFix128(Pd_oracle), borrowFactor: Fd) let Ce_post = Ce_pre - Ce_seize // position's total effective collateral after liquidation ($) let De_post = De_pre - De_seize // position's total effective debt after liquidation ($) let postHealth = FlowALPMath.healthComputation(effectiveCollateral: Ce_post, effectiveDebt: De_post) @@ -551,9 +551,9 @@ access(all) contract FlowALPv0 { message: "DEX/oracle price deviation too large. Dex price: \(Pcd_dex), Oracle price: \(Pcd_oracle)") // Execute the liquidation let seizedCollateral <- self._doLiquidation(pid: pid, repayment: <-repayment, debtType: debtType, seizeType: seizeType, seizeAmount: seizeAmount) - + self.unlockPosition(pid) - + return <- seizedCollateral } @@ -563,7 +563,7 @@ access(all) contract FlowALPv0 { access(self) fun _doLiquidation(pid: UInt64, repayment: @{FungibleToken.Vault}, debtType: Type, seizeType: Type, seizeAmount: UFix64): @{FungibleToken.Vault} { pre { !self.isPausedOrWarmup(): "Liquidations are paused by governance" - // position must have debt and collateral balance + // position must have debt and collateral balance } let repayAmount = repayment.balance @@ -1228,15 +1228,7 @@ access(all) contract FlowALPv0 { self._rebalancePositionNoLock(pid: id, force: true) } - // Create a capability to the Pool for the Position resource - // The Pool is stored in the FlowALPv0 contract account - let poolCap = FlowALPv0.account.capabilities.storage.issue( - FlowALPv0.PoolStoragePath - ) - - // Create and return the Position resource - - let position <- create Position(id: id, pool: poolCap) + let position <- create Position(id: id) self.unlockPosition(id) return <-position @@ -1670,7 +1662,7 @@ access(all) contract FlowALPv0 { // Validate constraint: non-zero rate requires swapper if insuranceRate > 0.0 { assert( - tsRef.getInsuranceSwapper() != nil, + tsRef.getInsuranceSwapper() != nil, message:"Cannot set non-zero insurance rate without an insurance swapper configured for \(tokenType.identifier)", ) } @@ -1689,13 +1681,13 @@ access(all) contract FlowALPv0 { self.isTokenSupported(tokenType: tokenType): "Unsupported token type" } let tsRef = self.state.borrowTokenState(tokenType) - ?? panic("Invariant: token state missing") + ?? panic("Invariant: token state missing") if let swapper = swapper { // Validate swapper types match assert(swapper.inType() == tokenType, message: "Swapper input type must match token type") assert(swapper.outType() == Type<@MOET.Vault>(), message: "Swapper output type must be MOET") - + } else { // cannot remove swapper if insurance rate > 0 assert( @@ -1779,7 +1771,7 @@ access(all) contract FlowALPv0 { let tsRef = self.state.borrowTokenState(tokenType) ?? panic("Invariant: token state missing") tsRef.setStabilityFeeRate(stabilityFeeRate) - + FlowALPEvents.emitStabilityFeeRateUpdated( poolUUID: self.uuid, tokenType: tokenType.identifier, @@ -1800,7 +1792,7 @@ access(all) contract FlowALPv0 { fundRef.balance >= amount, message: "Insufficient stability fund balance. Available: \(fundRef.balance), requested: \(amount)" ) - + let withdrawn <- fundRef.withdraw(amount: amount) recipient.deposit(from: <-withdrawn) @@ -2271,7 +2263,7 @@ access(all) contract FlowALPv0 { access(self) fun updateInterestRatesAndCollectInsurance(tokenType: Type) { let tokenState = self._borrowUpdatedTokenState(type: tokenType) tokenState.updateInterestRates() - + // Collect insurance if swapper is configured // Ensure reserves exist for this token type if !self.state.hasReserve(tokenType) { @@ -2353,7 +2345,7 @@ access(all) contract FlowALPv0 { access(all) fun getDefaultToken(): Type { return self.state.getDefaultToken() } - + /// Returns the deposit capacity and deposit capacity cap for a given token type access(all) fun getDepositCapacityInfo(type: Type): {String: UFix64} { let tokenState = self._borrowUpdatedTokenState(type: type) @@ -2413,24 +2405,13 @@ access(all) contract FlowALPv0 { /// The unique ID of the Position used to track deposits and withdrawals to the Pool access(all) let id: UInt64 - /// An authorized Capability to the Pool for which this Position was opened. - access(self) let pool: Capability - - init( - id: UInt64, - pool: Capability - ) { - pre { - pool.check(): - "Invalid Pool Capability provided - cannot construct Position" - } + init(id: UInt64) { self.id = id - self.pool = pool } /// Returns the balances (both positive and negative) for all tokens in this position. access(all) fun getBalances(): [FlowALPModels.PositionBalance] { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() return pool.getPositionDetails(pid: self.id).balances } @@ -2439,54 +2420,54 @@ access(all) contract FlowALPv0 { /// below its min health. If pullFromTopUpSource is false, the calculation will return the balance currently /// available without topping up the position. access(all) fun availableBalance(type: Type, pullFromTopUpSource: Bool): UFix64 { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() return pool.availableBalance(pid: self.id, type: type, pullFromTopUpSource: pullFromTopUpSource) } /// Returns the current health of the position access(all) fun getHealth(): UFix128 { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() return pool.positionHealth(pid: self.id) } /// Returns the Position's target health (unitless ratio ≥ 1.0) access(all) fun getTargetHealth(): UFix64 { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() let pos = pool.borrowPosition(pid: self.id) return FlowALPMath.toUFix64Round(pos.getTargetHealth()) } /// Sets the target health of the Position access(FlowALPModels.EPositionAdmin) fun setTargetHealth(targetHealth: UFix64) { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() let pos = pool.borrowPosition(pid: self.id) pos.setTargetHealth(UFix128(targetHealth)) } /// Returns the minimum health of the Position access(all) fun getMinHealth(): UFix64 { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() let pos = pool.borrowPosition(pid: self.id) return FlowALPMath.toUFix64Round(pos.getMinHealth()) } /// Sets the minimum health of the Position access(FlowALPModels.EPositionAdmin) fun setMinHealth(minHealth: UFix64) { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() let pos = pool.borrowPosition(pid: self.id) pos.setMinHealth(UFix128(minHealth)) } /// Returns the maximum health of the Position access(all) fun getMaxHealth(): UFix64 { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() let pos = pool.borrowPosition(pid: self.id) return FlowALPMath.toUFix64Round(pos.getMaxHealth()) } /// Sets the maximum health of the position access(FlowALPModels.EPositionAdmin) fun setMaxHealth(maxHealth: UFix64) { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() let pos = pool.borrowPosition(pid: self.id) pos.setMaxHealth(UFix128(maxHealth)) } @@ -2513,7 +2494,7 @@ access(all) contract FlowALPv0 { from: @{FungibleToken.Vault}, pushToDrawDownSink: Bool ) { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() pool.depositAndPush( pid: self.id, from: <-from, @@ -2538,7 +2519,7 @@ access(all) contract FlowALPv0 { amount: UFix64, pullFromTopUpSource: Bool ): @{FungibleToken.Vault} { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() return <- pool.withdrawAndPull( pid: self.id, type: type, @@ -2569,10 +2550,8 @@ access(all) contract FlowALPv0 { type: Type, pushToDrawDownSink: Bool ): {DeFiActions.Sink} { - let pool = self.pool.borrow()! return PositionSink( id: self.id, - pool: self.pool, type: type, pushToDrawDownSink: pushToDrawDownSink ) @@ -2600,10 +2579,8 @@ access(all) contract FlowALPv0 { type: Type, pullFromTopUpSource: Bool ): {DeFiActions.Source} { - let pool = self.pool.borrow()! return PositionSource( id: self.id, - pool: self.pool, type: type, pullFromTopUpSource: pullFromTopUpSource ) @@ -2620,7 +2597,7 @@ access(all) contract FlowALPv0 { /// /// Pass nil to configure the position to not push tokens when the Position exceeds its maximum health. access(FlowALPModels.EPositionAdmin) fun provideSink(sink: {DeFiActions.Sink}?) { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() pool.lockPosition(self.id) let pos = pool.borrowPosition(pid: self.id) pos.setDrawDownSink(sink) @@ -2636,7 +2613,7 @@ access(all) contract FlowALPv0 { /// /// Pass nil to configure the position to not pull tokens. access(FlowALPModels.EPositionAdmin) fun provideSource(source: {DeFiActions.Source}?) { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() pool.lockPosition(self.id) let pos = pool.borrowPosition(pid: self.id) pos.setTopUpSource(source) @@ -2652,7 +2629,7 @@ access(all) contract FlowALPv0 { /// of either cannot accept/provide sufficient funds for rebalancing, the rebalance will still occur but will /// not cause the position to reach its target health. access(FlowALPModels.EPosition | FlowALPModels.ERebalance) fun rebalance(force: Bool) { - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() pool.rebalancePosition(pid: self.id, force: force) } } @@ -2723,9 +2700,6 @@ access(all) contract FlowALPv0 { /// An optional DeFiActions.UniqueIdentifier that identifies this Sink with the DeFiActions stack its a part of access(contract) var uniqueID: DeFiActions.UniqueIdentifier? - /// An authorized Capability on the Pool for which the related Position is in - access(self) let pool: Capability - /// The ID of the position in the Pool access(self) let positionID: UInt64 @@ -2738,13 +2712,11 @@ access(all) contract FlowALPv0 { init( id: UInt64, - pool: Capability, type: Type, pushToDrawDownSink: Bool ) { self.uniqueID = nil self.positionID = id - self.pool = pool self.type = type self.pushToDrawDownSink = pushToDrawDownSink } @@ -2756,18 +2728,16 @@ access(all) contract FlowALPv0 { /// Returns the minimum capacity this Sink can accept as deposits access(all) fun minimumCapacity(): UFix64 { - return self.pool.check() ? UFix64.max : 0.0 + return UFix64.max } /// Deposits the funds from the provided Vault reference to the related Position access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) { - if let pool = self.pool.borrow() { - pool.depositAndPush( - pid: self.positionID, - from: <-from.withdraw(amount: from.balance), - pushToDrawDownSink: self.pushToDrawDownSink - ) - } + FlowALPv0._borrowPool().depositAndPush( + pid: self.positionID, + from: <-from.withdraw(amount: from.balance), + pushToDrawDownSink: self.pushToDrawDownSink + ) } access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { @@ -2797,9 +2767,6 @@ access(all) contract FlowALPv0 { /// An optional DeFiActions.UniqueIdentifier that identifies this Sink with the DeFiActions stack its a part of access(contract) var uniqueID: DeFiActions.UniqueIdentifier? - /// An authorized Capability on the Pool for which the related Position is in - access(self) let pool: Capability - /// The ID of the position in the Pool access(self) let positionID: UInt64 @@ -2812,13 +2779,11 @@ access(all) contract FlowALPv0 { init( id: UInt64, - pool: Capability, type: Type, pullFromTopUpSource: Bool ) { self.uniqueID = nil self.positionID = id - self.pool = pool self.type = type self.pullFromTopUpSource = pullFromTopUpSource } @@ -2830,12 +2795,7 @@ access(all) contract FlowALPv0 { /// Returns the minimum available this Source can provide on withdrawal access(all) fun minimumAvailable(): UFix64 { - if !self.pool.check() { - return 0.0 - } - - let pool = self.pool.borrow()! - return pool.availableBalance( + return FlowALPv0._borrowPool().availableBalance( pid: self.positionID, type: self.type, pullFromTopUpSource: self.pullFromTopUpSource @@ -2844,11 +2804,7 @@ access(all) contract FlowALPv0 { /// Withdraws up to the max amount as the sourceType Vault access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} { - if !self.pool.check() { - return <- DeFiActionsUtils.getEmptyVault(self.type) - } - - let pool = self.pool.borrow()! + let pool = FlowALPv0._borrowPool() let available = pool.availableBalance( pid: self.positionID, type: self.type, @@ -2862,10 +2818,8 @@ access(all) contract FlowALPv0 { amount: withdrawAmount, pullFromTopUpSource: self.pullFromTopUpSource ) - } else { - // Create an empty vault - this is a limitation we need to handle properly - return <- DeFiActionsUtils.getEmptyVault(self.type) } + return <- DeFiActionsUtils.getEmptyVault(self.type) } access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { @@ -2887,6 +2841,14 @@ access(all) contract FlowALPv0 { /* --- INTERNAL METHODS --- */ + /// Returns an authorized reference to the contract-managed Pool resource. + /// Used internally by Position, PositionSink, and PositionSource instead of + /// issuing per-position storage capabilities. + access(contract) view fun _borrowPool(): auth(FlowALPModels.EPosition) &Pool { + return self.account.storage.borrow(from: self.PoolStoragePath) + ?? panic("FlowALPv0 Pool not found at \(self.PoolStoragePath)") + } + /// Returns a reference to the contract account's MOET Minter resource access(self) view fun _borrowMOETMinter(): &MOET.Minter { return self.account.storage.borrow<&MOET.Minter>(from: MOET.AdminStoragePath)