Smart Contract
FlowYieldVaultsStrategies
A.b1d63873c3cc9f79.FlowYieldVaultsStrategies
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