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