Skip to content

Commit 9cf0944

Browse files
committed
address strategy comments
1 parent 6a2915e commit 9cf0944

2 files changed

Lines changed: 60 additions & 98 deletions

File tree

cadence/contracts/FlowYieldVaultsStrategiesV2.cdc

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ access(all) contract FlowYieldVaultsStrategiesV2 {
7272
}
7373
}
7474

75-
/// This strategy uses FUSDEV vault
75+
/// This strategy uses FUSDEV vault (Morpho ERC4626).
76+
/// Deposits collateral into a single FlowALP position, borrowing MOET as debt.
77+
/// MOET is swapped to PYUSD0 and deposited into the Morpho FUSDEV ERC4626 vault.
78+
/// Each strategy instance holds exactly one collateral type and one debt type (MOET).
79+
/// PYUSD0 (the FUSDEV vault's underlying asset) cannot be used as collateral.
7680
access(all) resource FUSDEVStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource {
7781
/// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
7882
/// specific Identifier to associated connectors on construction
@@ -85,10 +89,6 @@ access(all) contract FlowYieldVaultsStrategiesV2 {
8589
/// holds the position (e.g. during YieldVault burnCallback after close).
8690
access(self) var positionClosed: Bool
8791

88-
/// @TODO on the next iteration store yieldToMoetSwapper in the resource
89-
/// Swapper used to convert yield tokens back to MOET for debt repayment
90-
//access(self) let yieldToMoetSwapper: {DeFiActions.Swapper}
91-
9292
init(
9393
id: DeFiActions.UniqueIdentifier,
9494
collateralType: Type,
@@ -112,8 +112,13 @@ access(all) contract FlowYieldVaultsStrategiesV2 {
112112
if self.positionClosed { return 0.0 }
113113
return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
114114
}
115-
/// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
115+
/// Deposits up to the inner Sink's capacity from the provided authorized Vault reference.
116+
/// Only the single configured collateral type is accepted — one collateral type per position.
116117
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
118+
pre {
119+
from.getType() == self.sink.getSinkType():
120+
"FUSDEVStrategy position only accepts \(self.sink.getSinkType().identifier) as collateral, got \(from.getType().identifier)"
121+
}
117122
self.sink.depositCapacity(from: from)
118123
}
119124
/// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported,
@@ -147,7 +152,13 @@ access(all) contract FlowYieldVaultsStrategiesV2 {
147152
// Step 1: Get debt amounts - returns {Type: UFix64} dictionary
148153
let debtsByType = self.position.getTotalDebt()
149154

150-
// Step 2: Calculate total debt amount across all debt types
155+
// Enforce: one debt type per position
156+
assert(
157+
debtsByType.length <= 1,
158+
message: "FUSDEVStrategy position must have at most one debt type, found \(debtsByType.length)"
159+
)
160+
161+
// Step 2: Calculate total debt amount
151162
var totalDebtAmount: UFix64 = 0.0
152163
for debtAmount in debtsByType.values {
153164
totalDebtAmount = totalDebtAmount + debtAmount
@@ -158,9 +169,16 @@ access(all) contract FlowYieldVaultsStrategiesV2 {
158169
let resultVaults <- self.position.closePosition(
159170
repaymentSources: []
160171
)
161-
// Extract the first vault (should be collateral)
162-
assert(resultVaults.length > 0, message: "No vaults returned from closePosition")
163-
let collateralVault <- resultVaults.removeFirst()
172+
// With one collateral type and no debt the pool returns at most one vault.
173+
// Zero vaults is possible when the collateral balance is dust that rounds down
174+
// to zero (e.g. drawDownSink had no capacity, or token reserves were empty).
175+
assert(
176+
resultVaults.length <= 1,
177+
message: "Expected 0 or 1 collateral vault from closePosition, got \(resultVaults.length)"
178+
)
179+
let collateralVault <- resultVaults.length == 1
180+
? resultVaults.removeFirst()
181+
: <- DeFiActionsUtils.getEmptyVault(collateralType)
164182
destroy resultVaults
165183
self.positionClosed = true
166184
return <- collateralVault
@@ -187,30 +205,27 @@ access(all) contract FlowYieldVaultsStrategiesV2 {
187205
// Step 7: Close position - pool pulls exactly the debt amount from moetSource
188206
let resultVaults <- self.position.closePosition(repaymentSources: [moetSource])
189207

190-
// Extract all returned vaults
191-
assert(resultVaults.length > 0, message: "No vaults returned from closePosition")
208+
// With one collateral type and one debt type, the pool returns at most two vaults:
209+
// the collateral vault and optionally a MOET overpayment dust vault.
210+
assert(
211+
resultVaults.length >= 1 && resultVaults.length <= 2,
212+
message: "Expected 1 or 2 vaults from closePosition, got \(resultVaults.length)"
213+
)
192214

193-
// First vault should be collateral
194215
var collateralVault <- resultVaults.removeFirst()
216+
assert(
217+
collateralVault.getType() == collateralType,
218+
message: "First vault returned from closePosition must be collateral (\(collateralType.identifier)), got \(collateralVault.getType().identifier)"
219+
)
195220

196-
// Handle any overpayment dust (MOET) by swapping back to collateral
221+
// Handle any overpayment dust (MOET) returned as the second vault
197222
while resultVaults.length > 0 {
198223
let dustVault <- resultVaults.removeFirst()
199224
if dustVault.balance > 0.0 {
200225
if dustVault.getType() == collateralType {
201226
collateralVault.deposit(from: <-dustVault)
202227
} else {
203228
// @TODO implement swapping moet to collateral
204-
205-
// // Swap overpayment back to collateral using configured swapper
206-
// let moetToCollateralSwapperKey = FlowYieldVaultsStrategiesV2.getMoetToCollateralSwapperConfigKey(self.id()!)
207-
// let dustToCollateralSwapper = FlowYieldVaultsStrategiesV2.config[moetToCollateralSwapperKey] as! {DeFiActions.Swapper}?
208-
// ?? panic("No MOET→collateral swapper found for strategy \(self.id()!)")
209-
// let swappedCollateral <- dustToCollateralSwapper.swap(
210-
// quote: nil,
211-
// inVault: <-dustVault
212-
// )
213-
// collateralVault.deposit(from: <-swappedCollateral)
214229
destroy dustVault
215230
}
216231
} else {

cadence/contracts/mocks/MockStrategies.cdc

Lines changed: 21 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -130,88 +130,39 @@ access(all) contract MockStrategies {
130130
let ytSource = FlowYieldVaultsAutoBalancers.createExternalSource(id: self.id()!)
131131
?? panic("Could not create external source from AutoBalancer")
132132

133-
// Step 5: Withdraw ALL available YT from AutoBalancer to avoid losing funds when Strategy is destroyed
134-
let availableYt = ytSource.minimumAvailable()
135-
let totalYtVault <- ytSource.withdrawAvailable(maxAmount: availableYt)
136-
let totalYtAmount = totalYtVault.balance
137-
138-
// Step 6: Create YT→MOET swapper
139-
let ytToMoetSwapper = MockSwapper.Swapper(
140-
inVault: Type<@YieldToken.Vault>(),
141-
outVault: Type<@MOET.Vault>(),
142-
uniqueID: self.copyID()!
143-
)
144-
145-
// Step 7: Calculate how much MOET we can get from the available YT
146-
// Use quoteOut to see how much MOET we'll get from all available YT
147-
let ytQuote = ytToMoetSwapper.quoteOut(forProvided: totalYtAmount, reverse: false)
148-
let estimatedMoetFromYt = ytQuote.outAmount
149-
150-
// Step 8: Swap ALL YT to MOET to see how much we can cover
151-
var moetVault <- ytToMoetSwapper.swap(quote: ytQuote, inVault: <-totalYtVault)
152-
let moetFromYt = moetVault.balance
153-
154-
// Step 8: If YT didn't cover full debt, withdraw collateral to make up shortfall
155-
if moetFromYt < totalDebtAmount {
156-
let shortfall = totalDebtAmount - moetFromYt
157-
158-
// Create collateral→MOET swapper to convert collateral for debt repayment
159-
let collateralToMoetSwapper = MockSwapper.Swapper(
160-
inVault: collateralType,
161-
outVault: Type<@MOET.Vault>(),
133+
// Step 5: Build one SwapSource per debt type, each drawing from the AutoBalancer's YT.
134+
// ytSource is a struct (capability-backed), so each copy references the same underlying vault.
135+
// The pool drains each source sequentially to repay each debt type.
136+
var repaymentSources: [{DeFiActions.Source}] = []
137+
for debtType in debtsByType.keys {
138+
let ytToDebtSwapper = MockSwapper.Swapper(
139+
inVault: Type<@YieldToken.Vault>(),
140+
outVault: debtType,
162141
uniqueID: self.copyID()!
163142
)
164-
165-
// Calculate how much collateral we need to cover the shortfall
166-
let collateralQuote = collateralToMoetSwapper.quoteIn(
167-
forDesired: shortfall,
168-
reverse: false
169-
)
170-
171-
// Withdraw collateral from position to cover shortfall
172-
let collateralForDebt <- self.source.withdrawAvailable(maxAmount: collateralQuote.inAmount)
173-
174-
// Swap collateral to MOET and add to repayment vault
175-
let additionalMoet <- collateralToMoetSwapper.swap(
176-
quote: collateralQuote,
177-
inVault: <-collateralForDebt
178-
)
179-
moetVault.deposit(from: <-additionalMoet)
143+
repaymentSources.append(SwapConnectors.SwapSource(swapper: ytToDebtSwapper, source: ytSource, uniqueID: nil))
180144
}
181145

182-
// Step 9: Store MOET vault temporarily and create a VaultSource for closePosition.
183-
// closePosition now takes [{DeFiActions.Source}] instead of @[{FungibleToken.Vault}].
184-
let tempPath = StoragePath(identifier: "mockClosePositionMoet_\(self.uuid)")!
185-
MockStrategies.account.storage.save(<-(moetVault as! @MOET.Vault), to: tempPath)
186-
let moetCap = MockStrategies.account.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(tempPath)
187-
let moetSource = FungibleTokenConnectors.VaultSource(min: nil, withdrawVault: moetCap, uniqueID: nil)
188-
189-
// Step 10: Close position - pool pulls exactly the debt amount from moetSource
190-
let resultVaults <- self.position.closePosition(repaymentSources: [moetSource])
146+
// Step 6: Close position - pool pulls each debt type's amount from its corresponding SwapSource
147+
let resultVaults <- self.position.closePosition(repaymentSources: repaymentSources)
191148

192-
// Step 11: Recover any MOET not consumed by repayment from temp storage
193-
let remainingMoet <- MockStrategies.account.storage.load<@MOET.Vault>(from: tempPath)!
194-
195-
// Extract all returned vaults
149+
// Step 7: Extract collateral vault (first returned vault)
196150
assert(resultVaults.length > 0, message: "No vaults returned from closePosition")
197-
198-
// First vault should be collateral
199151
var collateralVault <- resultVaults.removeFirst()
200152

201-
// Swap any remaining MOET (not consumed by repayment) back to collateral
202-
if remainingMoet.balance > 0.0 {
203-
let moetToCollateralSwapper = MockSwapper.Swapper(
204-
inVault: Type<@MOET.Vault>(),
153+
// Step 8: Recover any remaining YT from the AutoBalancer and swap back to collateral
154+
let remainingYtAmount = ytSource.minimumAvailable()
155+
if remainingYtAmount > 0.0 {
156+
let remainingYt <- ytSource.withdrawAvailable(maxAmount: remainingYtAmount)
157+
let ytToCollateralSwapper = MockSwapper.Swapper(
158+
inVault: Type<@YieldToken.Vault>(),
205159
outVault: collateralType,
206160
uniqueID: self.copyID()!
207161
)
208-
let swappedCollateral <- moetToCollateralSwapper.swap(quote: nil, inVault: <-remainingMoet)
209-
collateralVault.deposit(from: <-swappedCollateral)
210-
} else {
211-
destroy remainingMoet
162+
collateralVault.deposit(from: <-ytToCollateralSwapper.swap(quote: nil, inVault: <-remainingYt))
212163
}
213164

214-
// Handle any additional vaults in resultVaults (e.g., overpayment credits) by swapping back to collateral
165+
// Step 9: Handle any additional vaults returned by closePosition (overpayments) by swapping to collateral
215166
while resultVaults.length > 0 {
216167
let dustVault <- resultVaults.removeFirst()
217168
if dustVault.balance > 0.0 && dustVault.getType() != collateralType {
@@ -220,11 +171,7 @@ access(all) contract MockStrategies {
220171
outVault: collateralType,
221172
uniqueID: self.copyID()!
222173
)
223-
let swappedCollateral <- dustToCollateralSwapper.swap(
224-
quote: nil,
225-
inVault: <-dustVault
226-
)
227-
collateralVault.deposit(from: <-swappedCollateral)
174+
collateralVault.deposit(from: <-dustToCollateralSwapper.swap(quote: nil, inVault: <-dustVault))
228175
} else {
229176
destroy dustVault
230177
}

0 commit comments

Comments
 (0)