From fdc137d28260c46ef261dcfbe25912663dac3d88 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:51:42 -0500 Subject: [PATCH 1/9] relax dust balance --- cadence/contracts/FlowALPv1.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowALPv1.cdc b/cadence/contracts/FlowALPv1.cdc index ec6ed8b2..16e7d9c7 100644 --- a/cadence/contracts/FlowALPv1.cdc +++ b/cadence/contracts/FlowALPv1.cdc @@ -3011,7 +3011,7 @@ access(all) contract FlowALPv1 { // This is applied to both credit and debit balances, with the main goal being to avoid dust positions. assert( - remainingBalance == 0.0 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), + remainingBalance <= 0.00001000 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), message: "Withdrawal would leave position below minimum balance requirement of \(self.globalLedger[type]!.minimumTokenBalancePerPosition). Remaining balance would be \(remainingBalance)." ) From ebe265a2131bc565e9ddde254c3b03b89c878772 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:46:24 -0500 Subject: [PATCH 2/9] restore relax --- cadence/contracts/FlowALPv1.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowALPv1.cdc b/cadence/contracts/FlowALPv1.cdc index ec6ed8b2..508e6f21 100644 --- a/cadence/contracts/FlowALPv1.cdc +++ b/cadence/contracts/FlowALPv1.cdc @@ -3011,7 +3011,7 @@ access(all) contract FlowALPv1 { // This is applied to both credit and debit balances, with the main goal being to avoid dust positions. assert( - remainingBalance == 0.0 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), + remainingBalance 0.00000300 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), message: "Withdrawal would leave position below minimum balance requirement of \(self.globalLedger[type]!.minimumTokenBalancePerPosition). Remaining balance would be \(remainingBalance)." ) From 8fd49d3f3a2647d8ae143c0dcff5c72fa190da1a Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:59:03 -0500 Subject: [PATCH 3/9] fix typo --- cadence/contracts/FlowALPv1.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowALPv1.cdc b/cadence/contracts/FlowALPv1.cdc index 508e6f21..5c3b98a2 100644 --- a/cadence/contracts/FlowALPv1.cdc +++ b/cadence/contracts/FlowALPv1.cdc @@ -3011,7 +3011,7 @@ access(all) contract FlowALPv1 { // This is applied to both credit and debit balances, with the main goal being to avoid dust positions. assert( - remainingBalance 0.00000300 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), + remainingBalance < 0.00000300 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), message: "Withdrawal would leave position below minimum balance requirement of \(self.globalLedger[type]!.minimumTokenBalancePerPosition). Remaining balance would be \(remainingBalance)." ) From 760b53d01d968180c404b9cdae0f290af8e42676 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:00:02 -0500 Subject: [PATCH 4/9] update ref --- FlowActions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowActions b/FlowActions index 527b2e5b..03fc42e4 160000 --- a/FlowActions +++ b/FlowActions @@ -1 +1 @@ -Subproject commit 527b2e5b5aac4093ee3dc71ab47ff62bf3283733 +Subproject commit 03fc42e4bb076f6badf1a2412a678dcbcf5a03d1 From 015a9d8d02da71cf757c717072c9672919473897 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:52:46 -0500 Subject: [PATCH 5/9] ref bridge exact --- FlowActions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowActions b/FlowActions index 03fc42e4..c21e486c 160000 --- a/FlowActions +++ b/FlowActions @@ -1 +1 @@ -Subproject commit 03fc42e4bb076f6badf1a2412a678dcbcf5a03d1 +Subproject commit c21e486cc0425a2c623d4fa113e5e2cef92cd937 From 972ac4c3e07f7074089e22751761d4c040c026c2 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 18 Feb 2026 23:10:49 -0500 Subject: [PATCH 6/9] update ref --- FlowActions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowActions b/FlowActions index c21e486c..bdf4f063 160000 --- a/FlowActions +++ b/FlowActions @@ -1 +1 @@ -Subproject commit c21e486cc0425a2c623d4fa113e5e2cef92cd937 +Subproject commit bdf4f063dd9db8fab64612cb08df1e522639e9ed From d9970e3d7aedffcb15eb1f953b299173c137f718 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:00:56 -0500 Subject: [PATCH 7/9] update ref --- FlowActions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowActions b/FlowActions index bdf4f063..2357ae77 160000 --- a/FlowActions +++ b/FlowActions @@ -1 +1 @@ -Subproject commit bdf4f063dd9db8fab64612cb08df1e522639e9ed +Subproject commit 2357ae770e6c5cccae65e2965f75b4fba0a64ed9 From f158d124320c8d5f946c16a4846e4f77f9f19de0 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:54:44 -0500 Subject: [PATCH 8/9] close position method --- cadence/contracts/FlowALPv0.cdc | 186 +++++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 1 deletion(-) diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index 72098ab6..dd78c9ba 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -57,6 +57,18 @@ access(all) contract FlowALPv0 { withdrawnUUID: UInt64 ) + /// Emitted when a position is closed via the closePosition() method. + /// This indicates a full position closure with debt repayment and collateral extraction. + access(all) event PositionClosed( + pid: UInt64, + poolUUID: UInt64, + repaymentAmount: UFix64, + repaymentType: Type, + collateralAmount: UFix64, + collateralType: Type, + finalDebt: UFix128 + ) + access(all) event Rebalanced( pid: UInt64, poolUUID: UInt64, @@ -3013,7 +3025,7 @@ access(all) contract FlowALPv0 { // This is applied to both credit and debit balances, with the main goal being to avoid dust positions. assert( - remainingBalance < 0.00000300 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), + remainingBalance = 0.0 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), message: "Withdrawal would leave position below minimum balance requirement of \(self.globalLedger[type]!.minimumTokenBalancePerPosition). Remaining balance would be \(remainingBalance)." ) @@ -3034,6 +3046,157 @@ access(all) contract FlowALPv0 { return <- withdrawn } + /// Closes a position by repaying all debt and returning all residual collateral. + /// This is the recommended way to close a leveraged position that may have dust residuals. + /// + /// This method follows the same pattern as withdrawAndPull(): + /// - Pulls ONLY the exact amount needed from the repayment source + /// - Returns ALL collateral (including dust) as a vault + /// + /// Steps: + /// 1. Calculates total debt + /// 2. Pulls exact repayment amount from source + /// 3. Deposits repayment to eliminate debt + /// 4. Verifies debt is fully repaid (near-zero) + /// 5. Withdraws ALL remaining collateral (including dust) + /// 6. Returns collateral vault + /// + /// @param pid: Position ID to close + /// @param repaymentSource: Source to pull debt repayment from (e.g., AutoBalancer with YT) + /// @param collateralType: Type of collateral to extract and return (e.g., FlowToken) + /// @return Vault containing all collateral including dust + access(EPosition) fun closePosition( + pid: UInt64, + repaymentSource: auth(FungibleToken.Withdraw) &{DeFiActions.Source}, + collateralType: Type + ): @{FungibleToken.Vault} { + pre { + !self.isPausedOrWarmup(): "Operations are paused by governance" + self.positions[pid] != nil: "Invalid position ID" + } + post { + self.positionLock[pid] == nil: "Position is not unlocked" + } + + self._lockPosition(pid) + + if self.debugLogging { + log(" [CONTRACT] closePosition(pid: \(pid), collateralType: \(collateralType.identifier))") + } + + // Step 1: Calculate total debt that needs to be repaid + let positionDetails = self.getPositionDetails(pid: pid) + var totalDebtAmount: UFix64 = 0.0 + var debtType: Type? = nil + + for balance in positionDetails.balances { + if balance.direction == BalanceDirection.Debit { + // Accumulate debt (assuming single debt type for now) + totalDebtAmount = totalDebtAmount + UFix64(balance.balance) + debtType = balance.vaultType + } + } + + // Verify we have debt to repay (or allow closing if already no debt) + if totalDebtAmount == 0.0 { + // No debt - just withdraw all collateral + let collateralBalance = self.buildPositionView(pid: pid).trueBalance(ofToken: collateralType) + let withdrawn <- self.withdrawAndPull( + pid: pid, + type: collateralType, + amount: UFix64(collateralBalance), + pullFromTopUpSource: false + ) + + emit PositionClosed( + pid: pid, + poolUUID: self.uuid, + repaymentAmount: 0.0, + repaymentType: collateralType, + collateralAmount: withdrawn.balance, + collateralType: collateralType, + finalDebt: 0.0 + ) + + self._unlockPosition(pid) + return <-withdrawn + } + + // Step 3: Pull EXACT amount needed from repayment source + let repaymentVault <- repaymentSource.withdrawAvailable(maxAmount: totalDebtAmount) + let actualRepayment = repaymentVault.balance + let repaymentType = repaymentVault.getType() + + // Verify source provided sufficient funds + assert( + actualRepayment >= totalDebtAmount * 0.9999, // Allow 0.01% slippage for rounding + message: "Repayment source provided insufficient funds: \(actualRepayment) < \(totalDebtAmount)" + ) + + // Step 4: Deposit repayment funds to eliminate debt + self._depositEffectsOnly(pid: pid, from: <-repaymentVault) + + // Step 5: Verify debt is fully repaid + let updatedDetails = self.getPositionDetails(pid: pid) + var totalEffectiveDebt: UFix128 = 0.0 + + for balance in updatedDetails.balances { + if balance.direction == BalanceDirection.Debit { + let tokenState = self._borrowUpdatedTokenState(type: balance.vaultType) + totalEffectiveDebt = totalEffectiveDebt + tokenState.effectiveDebt(debitBalance: balance.balance) + } + } + + // Require debt to be near-zero (allow tiny precision errors) + assert( + totalEffectiveDebt < UFix128(0.00001), + message: "Cannot close position - outstanding debt remains: \(totalEffectiveDebt) USD. ".concat( + "Repayment of \(actualRepayment) was insufficient.") + ) + + // Step 6: Calculate total collateral balance + var collateralBalance: UFix128 = 0.0 + + for balance in updatedDetails.balances { + if balance.vaultType == collateralType && balance.direction == BalanceDirection.Credit { + collateralBalance = balance.balance + break + } + } + + assert( + collateralBalance > 0.0, + message: "No collateral of type \(collateralType.identifier) found in position" + ) + + // Step 7: Withdraw ALL collateral (including dust via withdrawAndPull's dust sweeping) + // Note: Position is already locked, so we use withdrawAndPull which will try to lock again + // We need to unlock first, then let withdrawAndPull lock it + self._unlockPosition(pid) + + let collateral <- self.withdrawAndPull( + pid: pid, + type: collateralType, + amount: UFix64(collateralBalance), + pullFromTopUpSource: false + ) + + let finalCollateralAmount = collateral.balance + + // Emit event for position closure + emit PositionClosed( + pid: pid, + poolUUID: self.uuid, + repaymentAmount: actualRepayment, + repaymentType: repaymentType, + collateralAmount: finalCollateralAmount, + collateralType: collateralType, + finalDebt: totalEffectiveDebt + ) + + return <-collateral + } + /////////////////////// // POOL MANAGEMENT /////////////////////// @@ -3965,6 +4128,27 @@ access(all) contract FlowALPv0 { ) } + /// Closes the position by repaying all debt and returning all residual collateral. + /// This is the recommended way to close a leveraged position that may have dust residuals. + /// + /// This is a convenience wrapper that delegates to Pool.closePosition(). + /// See Pool.closePosition() for detailed documentation. + /// + /// @param repaymentSource: Source to pull debt repayment from (e.g., AutoBalancer with YT) + /// @param collateralType: Type of collateral to extract and return (e.g., FlowToken) + /// @return Vault containing all collateral including dust + access(FungibleToken.Withdraw) fun closePosition( + repaymentSource: auth(FungibleToken.Withdraw) &{DeFiActions.Source}, + collateralType: Type + ): @{FungibleToken.Vault} { + let pool = self.pool.borrow()! + return <- pool.closePosition( + pid: self.id, + repaymentSource: repaymentSource, + collateralType: collateralType + ) + } + /// Returns a new Sink for the given token type that will accept deposits of that token /// and update the position's collateral and/or debt accordingly. /// From 2a2e5526e48065742f476b199b869e591211f23a Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:12:43 -0500 Subject: [PATCH 9/9] fix assertion --- cadence/contracts/FlowALPv0.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index dd78c9ba..2f991b06 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -3025,7 +3025,7 @@ access(all) contract FlowALPv0 { // This is applied to both credit and debit balances, with the main goal being to avoid dust positions. assert( - remainingBalance = 0.0 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), + remainingBalance == 0.0 || self.positionSatisfiesMinimumBalance(type: type, balance: remainingBalance), message: "Withdrawal would leave position below minimum balance requirement of \(self.globalLedger[type]!.minimumTokenBalancePerPosition). Remaining balance would be \(remainingBalance)." )