Smart Contract

FlowYieldVaultsStrategies

A.b1d63873c3cc9f79.FlowYieldVaultsStrategies

Valid From

135,812,143

Deployed

1w ago
Feb 19, 2026, 10:35:24 AM UTC

Dependents

0 imports
1// standards
2import FungibleToken from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4import EVM from 0xe467b9dd11fa00df
5// DeFiActions
6import DeFiActionsUtils from 0x6d888f175c158410
7import DeFiActions from 0x6d888f175c158410
8import SwapConnectors from 0xe1a479f0cb911df9
9import FungibleTokenConnectors from 0x0c237e1265caa7a3
10// amm integration
11import UniswapV3SwapConnectors from 0xa7825d405ac89518
12import ERC4626SwapConnectors from 0x04f5ae6bef48c1fc
13import ERC4626Utils from 0x04f5ae6bef48c1fc
14// Lending protocol
15import FlowCreditMarket from 0x6b00ff876c299c61
16// FlowYieldVaults platform
17import FlowYieldVaultsClosedBeta from 0xb1d63873c3cc9f79
18import FlowYieldVaults from 0xb1d63873c3cc9f79
19import FlowYieldVaultsAutoBalancers from 0xb1d63873c3cc9f79
20// scheduler
21import FlowTransactionScheduler from 0xe467b9dd11fa00df
22import FlowYieldVaultsSchedulerRegistry from 0xb1d63873c3cc9f79
23// tokens
24import YieldToken from 0xb1d63873c3cc9f79
25import MOET from 0x6b00ff876c299c61
26// vm bridge
27import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
28import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
29import FlowEVMBridge from 0x1e4aa0b87d10b141
30// live oracles
31import ERC4626PriceOracles from 0x04f5ae6bef48c1fc
32// mocks
33import MockOracle from 0xb1d63873c3cc9f79
34import MockSwapper from 0xb1d63873c3cc9f79
35
36/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION
37/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
38///
39/// FlowYieldVaultsStrategies
40///
41/// This contract defines Strategies used in the FlowYieldVaults platform.
42///
43/// A Strategy instance can be thought of as objects wrapping a stack of DeFiActions connectors wired together to
44/// (optimally) generate some yield on initial deposits. Strategies can be simple such as swapping into a yield-bearing
45/// asset (such as stFLOW) or more complex DeFiActions stacks.
46///
47/// A StrategyComposer is tasked with the creation of a supported Strategy. It's within the stacking of DeFiActions
48/// connectors that the true power of the components lies.
49///
50access(all) contract FlowYieldVaultsStrategies {
51
52    access(all) let univ3FactoryEVMAddress: EVM.EVMAddress
53    access(all) let univ3RouterEVMAddress: EVM.EVMAddress
54    access(all) let univ3QuoterEVMAddress: EVM.EVMAddress
55    access(all) let yieldTokenEVMAddress: EVM.EVMAddress
56
57    /// Canonical StoragePath where the StrategyComposerIssuer should be stored
58    access(all) let IssuerStoragePath: StoragePath
59
60    /// This is the first Strategy implementation, wrapping a FlowCreditMarket Position along with its related Sink &
61    /// Source. While this object is a simple wrapper for the top-level collateralized position, the true magic of the
62    /// DeFiActions is in the stacking of the related connectors. This stacking logic can be found in the
63    /// TracerStrategyComposer construct.
64    access(all) resource TracerStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource {
65        /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
66        /// specific Identifier to associated connectors on construction
67        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
68        access(self) let position: FlowCreditMarket.Position
69        access(self) var sink: {DeFiActions.Sink}
70        access(self) var source: {DeFiActions.Source}
71
72        init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowCreditMarket.Position) {
73            self.uniqueID = id
74            self.position = position
75            self.sink = position.createSink(type: collateralType)
76            self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true)
77        }
78
79        // Inherited from FlowYieldVaults.Strategy default implementation
80        // access(all) view fun isSupportedCollateralType(_ type: Type): Bool
81
82        access(all) view fun getSupportedCollateralTypes(): {Type: Bool} {
83            return { self.sink.getSinkType(): true }
84        }
85        /// Returns the amount available for withdrawal via the inner Source
86        access(all) fun availableBalance(ofToken: Type): UFix64 {
87            return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
88        }
89        /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
90        access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
91            self.sink.depositCapacity(from: from)
92        }
93        /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported,
94        /// an empty Vault is returned.
95        access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} {
96            if ofToken != self.source.getSourceType() {
97                return <- DeFiActionsUtils.getEmptyVault(ofToken)
98            }
99            return <- self.source.withdrawAvailable(maxAmount: maxAmount)
100        }
101        /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer
102        access(contract) fun burnCallback() {
103            FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!)
104        }
105        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
106            return DeFiActions.ComponentInfo(
107                type: self.getType(),
108                id: self.id(),
109                innerComponents: []
110            )
111        }
112        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
113            return self.uniqueID
114        }
115        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
116            self.uniqueID = id
117        }
118    }
119
120    /// This StrategyComposer builds a TracerStrategy
121    access(all) resource TracerStrategyComposer : FlowYieldVaults.StrategyComposer {
122        /// Returns the Types of Strategies composed by this StrategyComposer
123        access(all) view fun getComposedStrategyTypes(): {Type: Bool} {
124            return { Type<@TracerStrategy>(): true }
125        }
126
127        /// Returns the Vault types which can be used to initialize a given Strategy
128        access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} {
129            return { Type<@FlowToken.Vault>(): true }
130        }
131
132        /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the
133        /// provided Vault type
134        access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} {
135            return { Type<@FlowToken.Vault>(): true }
136        }
137
138        /// Composes a Strategy of the given type with the provided funds
139        access(all) fun createStrategy(
140            _ type: Type,
141            uniqueID: DeFiActions.UniqueIdentifier,
142            withFunds: @{FungibleToken.Vault}
143        ): @{FlowYieldVaults.Strategy} {
144            // this PriceOracle is mocked and will be shared by all components used in the TracerStrategy
145            // TODO: add ERC4626 price oracle
146            let oracle = MockOracle.PriceOracle()
147
148            // assign token types
149
150            let moetTokenType: Type = Type<@MOET.Vault>()
151            let yieldTokenType = Type<@YieldToken.Vault>()
152            // assign collateral & flow token types
153            let collateralType = withFunds.getType()
154
155            // Create recurring config for automatic rebalancing
156            let recurringConfig = FlowYieldVaultsStrategies._createRecurringConfig(withID: uniqueID)
157
158            // configure and AutoBalancer for this stack with native recurring scheduling
159            let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer(
160                oracle: oracle,                 // used to determine value of deposits & when to rebalance
161                vaultType: yieldTokenType,      // the type of Vault held by the AutoBalancer
162                lowerThreshold: 0.95,           // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits
163                upperThreshold: 1.05,           // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits
164                rebalanceSink: nil,             // nil on init - will be set once a PositionSink is available
165                rebalanceSource: nil,           // nil on init - not set for TracerStrategy
166                recurringConfig: recurringConfig, // enables native AutoBalancer self-scheduling
167                uniqueID: uniqueID              // identifies AutoBalancer as part of this Strategy
168            )
169            // enables deposits of YieldToken to the AutoBalancer
170            let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
171            // enables withdrawals of YieldToken from the AutoBalancer
172            let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
173
174            // init Stable <> YIELD swappers
175            //
176            // Stable -> YieldToken
177            let stableToYieldSwapper = MockSwapper.Swapper(
178                inVault: moetTokenType,
179                outVault: yieldTokenType,
180                uniqueID: uniqueID
181            )
182            // YieldToken -> Stable
183            let yieldToStableSwapper = MockSwapper.Swapper(
184                inVault: yieldTokenType,
185                outVault: moetTokenType,
186                uniqueID: uniqueID
187            )
188
189            // init SwapSink directing swapped funds to AutoBalancer
190            //
191            // Swaps provided Stable to YieldToken & deposits to the AutoBalancer
192            let abaSwapSink = SwapConnectors.SwapSink(swapper: stableToYieldSwapper, sink: abaSink, uniqueID: uniqueID)
193            // Swaps YieldToken & provides swapped Stable, sourcing YieldToken from the AutoBalancer
194            let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToStableSwapper, source: abaSource, uniqueID: uniqueID)
195
196            // open a FlowCreditMarket position
197            let poolCap = FlowYieldVaultsStrategies.account.storage.load<Capability<auth(FlowCreditMarket.EParticipant, FlowCreditMarket.EPosition) &FlowCreditMarket.Pool>>(
198                from: FlowCreditMarket.PoolCapStoragePath
199            ) ?? panic("Missing pool capability")
200
201            let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap")
202
203            let pid = poolRef.createPosition(
204                funds: <-withFunds,
205                issuanceSink: abaSwapSink,
206                repaymentSource: abaSwapSource,
207                pushToDrawDownSink: true
208            )
209            let position = FlowCreditMarket.Position(id: pid, pool: poolCap)
210            FlowYieldVaultsStrategies.account.storage.save(poolCap, to: FlowCreditMarket.PoolCapStoragePath)
211
212            // get Sink & Source connectors relating to the new Position
213            let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true)
214            let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) // TODO: may need to be false
215
216            // init YieldToken -> FLOW Swapper
217            let yieldToFlowSwapper = MockSwapper.Swapper(
218                inVault: yieldTokenType,
219                outVault: collateralType,
220                uniqueID: uniqueID
221            )
222            // allows for YieldToken to be deposited to the Position
223            let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID)
224
225            // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value,
226            // recollateralizing the position
227            autoBalancer.setSink(positionSwapSink, updateSinkID: true)
228
229            // Use the same uniqueID passed to createStrategy so Strategy.burnCallback
230            // calls _cleanupAutoBalancer with the correct ID
231            return <-create TracerStrategy(
232                id: uniqueID,
233                collateralType: collateralType,
234                position: position
235            )
236        }
237    }
238
239    /// This strategy uses mUSDC vaults
240    access(all) resource mUSDCStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource {
241        /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
242        /// specific Identifier to associated connectors on construction
243        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
244        access(self) let position: FlowCreditMarket.Position
245        access(self) var sink: {DeFiActions.Sink}
246        access(self) var source: {DeFiActions.Source}
247
248        init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowCreditMarket.Position) {
249            self.uniqueID = id
250            self.position = position
251            self.sink = position.createSink(type: collateralType)
252            self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true)
253        }
254
255        // Inherited from FlowYieldVaults.Strategy default implementation
256        // access(all) view fun isSupportedCollateralType(_ type: Type): Bool
257
258        access(all) view fun getSupportedCollateralTypes(): {Type: Bool} {
259            return { self.sink.getSinkType(): true }
260        }
261        /// Returns the amount available for withdrawal via the inner Source
262        access(all) fun availableBalance(ofToken: Type): UFix64 {
263            return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
264        }
265        /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
266        access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
267            self.sink.depositCapacity(from: from)
268        }
269        /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported,
270        /// an empty Vault is returned.
271        access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} {
272            if ofToken != self.source.getSourceType() {
273                return <- DeFiActionsUtils.getEmptyVault(ofToken)
274            }
275            return <- self.source.withdrawAvailable(maxAmount: maxAmount)
276        }
277        /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer
278        access(contract) fun burnCallback() {
279            FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!)
280        }
281        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
282            return DeFiActions.ComponentInfo(
283                type: self.getType(),
284                id: self.id(),
285                innerComponents: [
286                    self.sink.getComponentInfo(),
287                    self.source.getComponentInfo()
288                ]
289            )
290        }
291        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
292            return self.uniqueID
293        }
294        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
295            self.uniqueID = id
296        }
297    }
298
299    /// This StrategyComposer builds a mUSDCStrategy
300    access(all) resource mUSDCStrategyComposer : FlowYieldVaults.StrategyComposer {
301        /// { Strategy Type: { Collateral Type: { String: AnyStruct } } }
302        access(self) let config: {Type: {Type: {String: AnyStruct}}}
303
304        init(_ config: {Type: {Type: {String: AnyStruct}}}) {
305            self.config = config
306        }
307
308        /// Returns the Types of Strategies composed by this StrategyComposer
309        access(all) view fun getComposedStrategyTypes(): {Type: Bool} {
310            let composed: {Type: Bool} = {}
311            for t in self.config.keys {
312                composed[t] = true
313            }
314            return composed
315        }
316
317        /// Returns the Vault types which can be used to initialize a given Strategy
318        access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} {
319            let supported: {Type: Bool} = {}
320            if let strategyConfig = &self.config[forStrategy] as &{Type: {String: AnyStruct}}? {
321                for collateralType in strategyConfig.keys {
322                    supported[collateralType] = true
323                }
324            }
325            return supported
326        }
327
328        /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the
329        /// provided Vault type
330        access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} {
331            let supportedInitVaults = self.getSupportedInitializationVaults(forStrategy: forStrategy)
332            if supportedInitVaults[initializedWith] == true {
333                return { initializedWith: true }
334            }
335            return {}
336        }
337
338        /// Composes a Strategy of the given type with the provided funds
339        /// TODO: Open up for multiple collateral types
340        access(all) fun createStrategy(
341            _ type: Type,
342            uniqueID: DeFiActions.UniqueIdentifier,
343            withFunds: @{FungibleToken.Vault}
344        ): @{FlowYieldVaults.Strategy} {
345            let collateralType = withFunds.getType()
346            let strategyConfig = self.config[type]
347                ?? panic("Could not find a config for Strategy \(type.identifier) initialized with \(collateralType.identifier)")
348            let collateralConfig = strategyConfig[collateralType]
349                ?? panic("Could not find config for collateral \(collateralType.identifier) when creating Strategy \(type.identifier)")
350
351            // assign token types & associated EVM Addresses
352            let moetTokenType: Type = Type<@MOET.Vault>()
353            let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType)
354                ?? panic("Token Vault type \(moetTokenType.identifier) has not yet been registered with the VMbridge")
355            let yieldTokenEVMAddress = collateralConfig["yieldTokenEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"yieldTokenEVMAddress\" in config")
356            let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress)
357                ?? panic("Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())")
358
359            // assign underlying asset EVM address & type - assumed to be stablecoin for the tracer strategy
360            let underlying4626AssetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress(
361                    vault: yieldTokenEVMAddress
362                ) ?? panic("Could not get the underlying asset's EVM address for ERC4626Vault \(yieldTokenEVMAddress.toString())")
363            let underlying4626AssetType = FlowEVMBridgeConfig.getTypeAssociated(with: underlying4626AssetEVMAddress)
364                ?? panic("Could not retrieve the VM Bridge associated Type for the ERC4626 underlying asset \(underlying4626AssetEVMAddress.toString())")
365
366            // create the oracle for the assets to be held in the AutoBalancer retrieving the NAV of the 4626 vault
367            let yieldTokenOracle = ERC4626PriceOracles.PriceOracle(
368                    vault: yieldTokenEVMAddress,
369                    asset: underlying4626AssetType,
370                    // asset: moetTokenType, // TODO: make a composite oracle that returns the price denominated in MOET
371                    uniqueID: uniqueID
372                )
373
374            // Create recurring config for automatic rebalancing
375            let recurringConfig = FlowYieldVaultsStrategies._createRecurringConfig(withID: uniqueID)
376
377            // configure and AutoBalancer for this stack with native recurring scheduling
378            let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer(
379                    oracle: yieldTokenOracle,       // used to determine value of deposits & when to rebalance
380                    vaultType: yieldTokenType,      // the type of Vault held by the AutoBalancer
381                    lowerThreshold: 0.95,           // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits
382                    upperThreshold: 1.05,           // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits
383                    rebalanceSink: nil,             // nil on init - will be set once a PositionSink is available
384                    rebalanceSource: nil,           // nil on init - not set for TracerStrategy
385                    recurringConfig: recurringConfig, // enables native AutoBalancer self-scheduling
386                    uniqueID: uniqueID              // identifies AutoBalancer as part of this Strategy
387                )
388            // enables deposits of YieldToken to the AutoBalancer
389            let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
390            // enables withdrawals of YieldToken from the AutoBalancer
391            let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
392
393            // create MOET <-> YIELD swappers
394            //
395            // get Uniswap V3 addresses from config
396            let univ3FactoryEVMAddress = collateralConfig["univ3FactoryEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3FactoryEVMAddress\" in config")
397            let univ3RouterEVMAddress = collateralConfig["univ3RouterEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3RouterEVMAddress\" in config")
398            let univ3QuoterEVMAddress = collateralConfig["univ3QuoterEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3QuoterEVMAddress\" in config")
399            // MOET -> YIELD - MOET can swap to YieldToken via two primary routes
400            // - via AMM swap pairing MOET <-> YIELD
401            // - via 4626 vault, swapping first to underlying asset then depositing to the 4626 vault
402            // MOET -> YIELD high-level Swapper then contains
403            //     - MultiSwapper aggregates across two sub-swappers
404            //         - MOET -> YIELD (UniV3 Swapper)
405            //         - SequentialSwapper
406            //             - MOET -> UNDERLYING (UniV3 Swapper)
407            //             - UNDERLYING -> YIELD (ERC4626Swapper)
408            let moetToYieldAMMSwapper = UniswapV3SwapConnectors.Swapper(
409                    factoryAddress: univ3FactoryEVMAddress,
410                    routerAddress: univ3RouterEVMAddress,
411                    quoterAddress: univ3QuoterEVMAddress,
412                    tokenPath: [moetTokenEVMAddress, yieldTokenEVMAddress],
413                    feePath: [100],
414                    inVault: moetTokenType,
415                    outVault: yieldTokenType,
416                    coaCapability: FlowYieldVaultsStrategies._getCOACapability(),
417                    uniqueID: uniqueID
418                )
419            // Swap MOET -> UNDERLYING via AMM
420            let moetToUnderlyingAssetSwapper = UniswapV3SwapConnectors.Swapper(
421                    factoryAddress: univ3FactoryEVMAddress,
422                    routerAddress: univ3RouterEVMAddress,
423                    quoterAddress: univ3QuoterEVMAddress,
424                    tokenPath: [moetTokenEVMAddress, underlying4626AssetEVMAddress],
425                    feePath: [100],
426                    inVault: moetTokenType,
427                    outVault: underlying4626AssetType,
428                    coaCapability: FlowYieldVaultsStrategies._getCOACapability(),
429                    uniqueID: uniqueID
430                )
431            // Swap UNDERLYING -> YIELD via ERC4626 Vault
432            let underlyingTo4626Swapper = ERC4626SwapConnectors.Swapper(
433                    asset: underlying4626AssetType,
434                    vault: yieldTokenEVMAddress,
435                    coa: FlowYieldVaultsStrategies._getCOACapability(),
436                    feeSource: FlowYieldVaultsStrategies._createFeeSource(withID: uniqueID),
437                    uniqueID: uniqueID
438                )
439            // Compose v3 swapper & 4626 swapper into sequential swapper for MOET -> UNDERLYING -> YIELD
440            let moetToYieldSeqSwapper = SwapConnectors.SequentialSwapper(
441                    swappers: [moetToUnderlyingAssetSwapper, underlyingTo4626Swapper],
442                    uniqueID: uniqueID
443                )
444            // Finally, add the two MOET -> YIELD swappers into an aggregate MultiSwapper
445            let moetToYieldSwapper = SwapConnectors.MultiSwapper(
446                    inVault: moetTokenType,
447                    outVault: yieldTokenType,
448                    swappers: [moetToYieldAMMSwapper, moetToYieldSeqSwapper],
449                    uniqueID: uniqueID
450                )
451
452            // YIELD -> MOET
453            // - Targets the MOET <-> YIELD pool as the only route since withdraws from the ERC4626 Vault are async
454            let yieldToMOETSwapper = UniswapV3SwapConnectors.Swapper(
455                    factoryAddress: univ3FactoryEVMAddress,
456                    routerAddress: univ3RouterEVMAddress,
457                    quoterAddress: univ3QuoterEVMAddress,
458                    tokenPath: [yieldTokenEVMAddress, moetTokenEVMAddress],
459                    feePath: [100],
460                    inVault: yieldTokenType,
461                    outVault: moetTokenType,
462                    coaCapability: FlowYieldVaultsStrategies._getCOACapability(),
463                    uniqueID: uniqueID
464                )
465
466            // init SwapSink directing swapped funds to AutoBalancer
467            //
468            // Swaps provided MOET to YIELD & deposits to the AutoBalancer
469            let abaSwapSink = SwapConnectors.SwapSink(swapper: moetToYieldSwapper, sink: abaSink, uniqueID: uniqueID)
470            // Swaps YIELD & provides swapped MOET, sourcing YIELD from the AutoBalancer
471            let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToMOETSwapper, source: abaSource, uniqueID: uniqueID)
472
473            // open a FlowCreditMarket position
474            let poolCap = FlowYieldVaultsStrategies.account.storage.copy<Capability<auth(FlowCreditMarket.EParticipant, FlowCreditMarket.EPosition) &FlowCreditMarket.Pool>>(
475                    from: FlowCreditMarket.PoolCapStoragePath
476                ) ?? panic("Missing or invalid pool capability")
477            let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap")
478
479            let pid = poolRef.createPosition(
480                    funds: <-withFunds,
481                    issuanceSink: abaSwapSink,
482                    repaymentSource: abaSwapSource,
483                    pushToDrawDownSink: true
484                )
485            let position = FlowCreditMarket.Position(id: pid, pool: poolCap)
486
487            // get Sink & Source connectors relating to the new Position
488            let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true)
489            let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true)
490
491            // init YieldToken -> FLOW Swapper
492            //
493            // get UniswapV3 path configs
494            let collateralUniV3AddressPathConfig = collateralConfig["yieldToCollateralUniV3AddressPaths"] as? {Type: [EVM.EVMAddress]}
495                ?? panic("Could not find UniswapV3 address paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)")
496            let uniV3AddressPath = collateralUniV3AddressPathConfig[collateralType]
497                ?? panic("Could not find UniswapV3 address path for collateral type \(collateralType.identifier)")
498            assert(uniV3AddressPath.length > 1, message: "Invalid Uniswap V3 swap path length of \(uniV3AddressPath.length)")
499            assert(uniV3AddressPath[0].equals(yieldTokenEVMAddress),
500                message: "UniswapV3 swap path does not match - expected path[0] to be \(yieldTokenEVMAddress.toString()) but found \(uniV3AddressPath[0].toString())")
501            let collateralUniV3FeePathConfig = collateralConfig["yieldToCollateralUniV3FeePaths"] as? {Type: [UInt32]}
502                ?? panic("Could not find UniswapV3 fee paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)")
503            let uniV3FeePath = collateralUniV3FeePathConfig[collateralType]
504                ?? panic("Could not find UniswapV3 fee path for collateral type \(collateralType.identifier)")
505            assert(uniV3FeePath.length > 0, message: "Invalid Uniswap V3 fee path length of \(uniV3FeePath.length)")
506            // initialize the swapper used for recollateralization of the lending position as YIELD increases in value
507            let yieldToFlowSwapper = UniswapV3SwapConnectors.Swapper(
508                    factoryAddress: univ3FactoryEVMAddress,
509                    routerAddress: univ3RouterEVMAddress,
510                    quoterAddress: univ3QuoterEVMAddress,
511                    tokenPath: uniV3AddressPath,
512                    feePath: uniV3FeePath,
513                    inVault: yieldTokenType,
514                    outVault: collateralType,
515                    coaCapability: FlowYieldVaultsStrategies._getCOACapability(),
516                    uniqueID: uniqueID
517                )
518            // allows for YIELD to be deposited to the Position as the collateral basis
519            let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID)
520
521            // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value, recollateralizing
522            // the position
523            autoBalancer.setSink(positionSwapSink, updateSinkID: true)
524
525            // Use the same uniqueID passed to createStrategy so Strategy.burnCallback
526            // calls _cleanupAutoBalancer with the correct ID
527            return <-create mUSDCStrategy(
528                id: uniqueID,
529                collateralType: collateralType,
530                position: position
531            )
532        }
533    }
534
535    access(all) entitlement Configure
536
537    /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which
538    /// may utilize resource consumption (i.e. account storage). Since TracerStrategy creation consumes account storage
539    /// via configured AutoBalancers
540    access(all) resource StrategyComposerIssuer : FlowYieldVaults.StrategyComposerIssuer {
541        /// { StrategyComposer Type: { Strategy Type: { Collateral Type: { String: AnyStruct } } } }
542        access(all) let configs: {Type: {Type: {Type: {String: AnyStruct}}}}
543
544        init(configs: {Type: {Type: {Type: {String: AnyStruct}}}}) {
545            self.configs = configs
546        }
547
548        access(all) view fun getSupportedComposers(): {Type: Bool} {
549            return {
550                Type<@mUSDCStrategyComposer>(): true,
551                Type<@TracerStrategyComposer>(): true
552            }
553        }
554        access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} {
555            pre {
556                self.getSupportedComposers()[type] == true:
557                "Unsupported StrategyComposer \(type.identifier) requested"
558                (&self.configs[type] as &{Type: {Type: {String: AnyStruct}}}?) != nil:
559                "Could not find config for StrategyComposer \(type.identifier)"
560            }
561            switch type {
562            case Type<@mUSDCStrategyComposer>():
563                return <- create mUSDCStrategyComposer(self.configs[type]!)
564            case Type<@TracerStrategyComposer>():
565                return <- create TracerStrategyComposer()
566            default:
567                panic("Unsupported StrategyComposer \(type.identifier) requested")
568            }
569        }
570        access(Configure) fun upsertConfigFor(composer: Type, config: {Type: {Type: {String: AnyStruct}}}) {
571            pre {
572                self.getSupportedComposers()[composer] == true:
573                "Unsupported StrategyComposer Type \(composer.identifier)"
574            }
575            for stratType in config.keys {
576                assert(stratType.isSubtype(of: Type<@{FlowYieldVaults.Strategy}>()),
577                    message: "Invalid config key \(stratType.identifier) - not a FlowYieldVaults.Strategy Type")
578                for collateralType in config[stratType]!.keys {
579                    assert(collateralType.isSubtype(of: Type<@{FungibleToken.Vault}>()),
580                        message: "Invalid config key at config[\(stratType.identifier)] - \(collateralType.identifier) is not a FungibleToken.Vault")
581                }
582            }
583            self.configs[composer] = config
584        }
585    }
586
587    /// Returns the COA capability for this account
588    /// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors
589    access(self)
590    fun _getCOACapability(): Capability<auth(EVM.Call, EVM.Bridge, EVM.Owner) &EVM.CadenceOwnedAccount> {
591        let coaCap = self.account.capabilities.storage.issue<auth(EVM.Call, EVM.Bridge, EVM.Owner) &EVM.CadenceOwnedAccount>(/storage/evm)
592        assert(coaCap.check(), message: "Could not issue COA capability")
593        return coaCap
594    }
595
596    /// Returns a FungibleTokenConnectors.VaultSinkAndSource used to subsidize cross VM token movement in contract-
597    /// defined strategies.
598    access(self)
599    fun _createFeeSource(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} {
600        let capPath = /storage/strategiesFeeSource
601        if self.account.storage.type(at: capPath) == nil {
602            let cap = self.account.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(/storage/flowTokenVault)
603            self.account.storage.save(cap, to: capPath)
604        }
605        let vaultCap = self.account.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>>(from: capPath)
606            ?? panic("Could not find fee source Capability at \(capPath)")
607        return FungibleTokenConnectors.VaultSinkAndSource(
608            min: nil,
609            max: nil,
610            vault: vaultCap,
611            uniqueID: withID
612        )
613    }
614
615    /// Creates an AutoBalancerRecurringConfig for scheduled rebalancing.
616    /// The txnFunder uses the contract's FlowToken vault to pay for scheduling fees.
617    access(self)
618    fun _createRecurringConfig(withID: DeFiActions.UniqueIdentifier?): DeFiActions.AutoBalancerRecurringConfig {
619        // Create txnFunder that can provide/accept FLOW for scheduling fees
620        let txnFunder = self._createTxnFunder(withID: withID)
621
622        return DeFiActions.AutoBalancerRecurringConfig(
623            interval: 60 * 10,  // Rebalance every 10 minutes
624            priority: FlowTransactionScheduler.Priority.Medium,
625            executionEffort: 999,
626            forceRebalance: false,
627            txnFunder: txnFunder
628        )
629    }
630
631    /// Creates a Sink+Source for the AutoBalancer to use for scheduling fees
632    access(self)
633    fun _createTxnFunder(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} {
634        let capPath = /storage/autoBalancerTxnFunder
635        if self.account.storage.type(at: capPath) == nil {
636            let cap = self.account.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(/storage/flowTokenVault)
637            self.account.storage.save(cap, to: capPath)
638        }
639        let vaultCap = self.account.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>>(from: capPath)
640            ?? panic("Could not find txnFunder Capability at \(capPath)")
641        return FungibleTokenConnectors.VaultSinkAndSource(
642            min: nil,
643            max: nil,
644            vault: vaultCap,
645            uniqueID: withID
646        )
647    }
648
649    init(
650        univ3FactoryEVMAddress: String,
651        univ3RouterEVMAddress: String,
652        univ3QuoterEVMAddress: String,
653        yieldTokenEVMAddress: String,
654        recollateralizationUniV3AddressPath: [String],
655        recollateralizationUniV3FeePath: [UInt32],
656    ) {
657        self.univ3FactoryEVMAddress = EVM.addressFromString(univ3FactoryEVMAddress)
658        self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress)
659        self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress)
660        self.yieldTokenEVMAddress = EVM.addressFromString(yieldTokenEVMAddress)
661        self.IssuerStoragePath = StoragePath(identifier: "FlowYieldVaultsStrategyComposerIssuer_\(self.account.address)")!
662
663        let initialCollateralType = Type<@FlowToken.Vault>()
664        let moetType = Type<@MOET.Vault>()
665        let moetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>())
666            ?? panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge")
667        let yieldTokenEVMAddress = EVM.addressFromString(yieldTokenEVMAddress)
668
669        let swapAddressPath: [EVM.EVMAddress] = []
670        for hex in recollateralizationUniV3AddressPath {
671            swapAddressPath.append(EVM.addressFromString(hex))
672        }
673
674        let configs: {Type: {Type: {Type: {String: AnyStruct}}}} = {
675                Type<@mUSDCStrategyComposer>(): {
676                    Type<@mUSDCStrategy>(): {
677                        initialCollateralType: {
678                            "univ3FactoryEVMAddress": self.univ3FactoryEVMAddress,
679                            "univ3RouterEVMAddress": self.univ3RouterEVMAddress,
680                            "univ3QuoterEVMAddress": self.univ3QuoterEVMAddress,
681                            "yieldTokenEVMAddress": self.yieldTokenEVMAddress,
682                            "yieldToCollateralUniV3AddressPaths": {
683                                initialCollateralType: swapAddressPath
684                            },
685                            "yieldToCollateralUniV3FeePaths": {
686                                initialCollateralType: recollateralizationUniV3FeePath
687                            }
688                        }
689                    }
690                },
691                Type<@TracerStrategyComposer>(): {
692                    Type<@TracerStrategy>(): {}
693                }
694            }
695        self.account.storage.save(<-create StrategyComposerIssuer(configs: configs), to: self.IssuerStoragePath)
696
697        // TODO: this is temporary until we have a better way to pass user's COAs to inner connectors
698        // create a COA in this account
699        if self.account.storage.type(at: /storage/evm) == nil {
700            self.account.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm)
701            let cap = self.account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(/storage/evm)
702            self.account.capabilities.publish(cap, at: /public/evm)
703        }
704    }
705}
706