Smart Contract
PMStrategiesV1
A.b1d63873c3cc9f79.PMStrategiesV1
1// standards
2import FungibleToken from 0xf233dcee88fe0abe
3import EVM from 0xe467b9dd11fa00df
4// DeFiActions
5import DeFiActionsUtils from 0x6d888f175c158410
6import DeFiActions from 0x6d888f175c158410
7import SwapConnectors from 0xe1a479f0cb911df9
8import FungibleTokenConnectors from 0x0c237e1265caa7a3
9// amm integration
10import UniswapV3SwapConnectors from 0xa7825d405ac89518
11import ERC4626SwapConnectors from 0x04f5ae6bef48c1fc
12import MorphoERC4626SwapConnectors from 0x251032a66e9700ef
13import ERC4626Utils from 0x04f5ae6bef48c1fc
14// FlowYieldVaults platform
15import FlowYieldVaults from 0xb1d63873c3cc9f79
16import FlowYieldVaultsAutoBalancers from 0xb1d63873c3cc9f79
17// vm bridge
18import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
19// live oracles
20import ERC4626PriceOracles from 0x04f5ae6bef48c1fc
21
22/// PMStrategiesV1
23///
24/// This contract defines Strategies used in the FlowYieldVaults platform.
25///
26/// A Strategy instance can be thought of as objects wrapping a stack of DeFiActions connectors wired together to
27/// (optimally) generate some yield on initial deposits. Strategies can be simple such as swapping into a yield-bearing
28/// asset (such as stFLOW) or more complex DeFiActions stacks.
29///
30/// A StrategyComposer is tasked with the creation of a supported Strategy. It's within the stacking of DeFiActions
31/// connectors that the true power of the components lies.
32///
33access(all) contract PMStrategiesV1 {
34
35 access(all) let univ3FactoryEVMAddress: EVM.EVMAddress
36 access(all) let univ3RouterEVMAddress: EVM.EVMAddress
37 access(all) let univ3QuoterEVMAddress: EVM.EVMAddress
38
39 /// Canonical StoragePath where the StrategyComposerIssuer should be stored
40 access(all) let IssuerStoragePath: StoragePath
41
42 /// Contract-level config for extensibility (not yet used)
43 access(self) let config: {String: {String: AnyStruct}}
44
45 /// This strategy uses syWFLOWv vaults
46 access(all) resource syWFLOWvStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource {
47 /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
48 /// specific Identifier to associated connectors on construction
49 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
50
51 /// User-facing deposit connector
52 access(self) var sink: {DeFiActions.Sink}
53 /// User-facing withdrawal connector
54 access(self) var source: {DeFiActions.Source}
55
56 init(
57 id: DeFiActions.UniqueIdentifier,
58 sink: {DeFiActions.Sink},
59 source: {DeFiActions.Source}
60 ) {
61 self.uniqueID = id
62 self.sink = sink
63 self.source = source
64 }
65
66 // Inherited from FlowYieldVaults.Strategy default implementation
67 // access(all) view fun isSupportedCollateralType(_ type: Type): Bool
68
69 access(all) view fun getSupportedCollateralTypes(): {Type: Bool} {
70 return { self.sink.getSinkType(): true }
71 }
72 /// Returns the amount available for withdrawal via the inner Source
73 access(all) fun availableBalance(ofToken: Type): UFix64 {
74 return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
75 }
76 /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
77 access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
78 self.sink.depositCapacity(from: from)
79 }
80 /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported,
81 /// an empty Vault is returned.
82 access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} {
83 if ofToken != self.source.getSourceType() {
84 return <- DeFiActionsUtils.getEmptyVault(ofToken)
85 }
86 return <- self.source.withdrawAvailable(maxAmount: maxAmount)
87 }
88 /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer
89 access(contract) fun burnCallback() {
90 FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!)
91 }
92 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
93 return DeFiActions.ComponentInfo(
94 type: self.getType(),
95 id: self.id(),
96 innerComponents: [
97 self.sink.getComponentInfo(),
98 self.source.getComponentInfo()
99 ]
100 )
101 }
102 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
103 return self.uniqueID
104 }
105 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
106 self.uniqueID = id
107 }
108 }
109
110 /// This strategy uses tauUSDF vaults (Tau Labs USDF Vault)
111 access(all) resource tauUSDFvStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource {
112 /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
113 /// specific Identifier to associated connectors on construction
114 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
115
116 /// User-facing deposit connector
117 access(self) var sink: {DeFiActions.Sink}
118 /// User-facing withdrawal connector
119 access(self) var source: {DeFiActions.Source}
120
121 init(
122 id: DeFiActions.UniqueIdentifier,
123 sink: {DeFiActions.Sink},
124 source: {DeFiActions.Source}
125 ) {
126 self.uniqueID = id
127 self.sink = sink
128 self.source = source
129 }
130
131 // Inherited from FlowYieldVaults.Strategy default implementation
132 // access(all) view fun isSupportedCollateralType(_ type: Type): Bool
133
134 access(all) view fun getSupportedCollateralTypes(): {Type: Bool} {
135 return { self.sink.getSinkType(): true }
136 }
137 /// Returns the amount available for withdrawal via the inner Source
138 access(all) fun availableBalance(ofToken: Type): UFix64 {
139 return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
140 }
141 /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
142 access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
143 self.sink.depositCapacity(from: from)
144 }
145 /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported,
146 /// an empty Vault is returned.
147 access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} {
148 if ofToken != self.source.getSourceType() {
149 return <- DeFiActionsUtils.getEmptyVault(ofToken)
150 }
151 return <- self.source.withdrawAvailable(maxAmount: maxAmount)
152 }
153 /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer
154 access(contract) fun burnCallback() {
155 FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!)
156 }
157 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
158 return DeFiActions.ComponentInfo(
159 type: self.getType(),
160 id: self.id(),
161 innerComponents: [
162 self.sink.getComponentInfo(),
163 self.source.getComponentInfo()
164 ]
165 )
166 }
167 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
168 return self.uniqueID
169 }
170 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
171 self.uniqueID = id
172 }
173 }
174
175 /// This strategy uses FUSDEV vaults (Flow USD Expeditionary Vault)
176 access(all) resource FUSDEVStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource {
177 /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
178 /// specific Identifier to associated connectors on construction
179 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
180
181 /// User-facing deposit connector
182 access(self) var sink: {DeFiActions.Sink}
183 /// User-facing withdrawal connector
184 access(self) var source: {DeFiActions.Source}
185
186 init(
187 id: DeFiActions.UniqueIdentifier,
188 sink: {DeFiActions.Sink},
189 source: {DeFiActions.Source}
190 ) {
191 self.uniqueID = id
192 self.sink = sink
193 self.source = source
194 }
195
196 // Inherited from FlowYieldVaults.Strategy default implementation
197 // access(all) view fun isSupportedCollateralType(_ type: Type): Bool
198
199 access(all) view fun getSupportedCollateralTypes(): {Type: Bool} {
200 return { self.sink.getSinkType(): true }
201 }
202 /// Returns the amount available for withdrawal via the inner Source
203 access(all) fun availableBalance(ofToken: Type): UFix64 {
204 return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
205 }
206 /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
207 access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
208 self.sink.depositCapacity(from: from)
209 }
210 /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported,
211 /// an empty Vault is returned.
212 access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} {
213 if ofToken != self.source.getSourceType() {
214 return <- DeFiActionsUtils.getEmptyVault(ofToken)
215 }
216 return <- self.source.withdrawAvailable(maxAmount: maxAmount)
217 }
218 /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer
219 access(contract) fun burnCallback() {
220 FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!)
221 }
222 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
223 return DeFiActions.ComponentInfo(
224 type: self.getType(),
225 id: self.id(),
226 innerComponents: [
227 self.sink.getComponentInfo(),
228 self.source.getComponentInfo()
229 ]
230 )
231 }
232 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
233 return self.uniqueID
234 }
235 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
236 self.uniqueID = id
237 }
238 }
239
240 /// StrategyComposer for ERC4626 vault strategies (e.g., syWFLOWvStrategy, tauUSDFvStrategy, FUSDEVStrategy).
241 access(all) resource ERC4626VaultStrategyComposer : FlowYieldVaults.StrategyComposer {
242 /// { Strategy Type: { Collateral Type: { String: AnyStruct } } }
243 access(self) let config: {Type: {Type: {String: AnyStruct}}}
244
245 init(_ config: {Type: {Type: {String: AnyStruct}}}) {
246 self.config = config
247 }
248
249 access(all) view fun getComposedStrategyTypes(): {Type: Bool} {
250 let composed: {Type: Bool} = {}
251 for t in self.config.keys {
252 composed[t] = true
253 }
254 return composed
255 }
256
257 access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} {
258 let strategyConfig = self.config[forStrategy]
259 if strategyConfig == nil {
260 return {}
261 }
262 // Return all supported collateral types from config
263 let supported: {Type: Bool} = {}
264 for collateralType in strategyConfig!.keys {
265 supported[collateralType] = true
266 }
267 return supported
268 }
269
270 /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the
271 /// provided Vault type
272 access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} {
273 let supportedInitVaults = self.getSupportedInitializationVaults(forStrategy: forStrategy)
274 if supportedInitVaults[initializedWith] == true {
275 return { initializedWith: true }
276 }
277 return {}
278 }
279
280 /// Composes a Strategy of the given type with the provided funds
281 access(all) fun createStrategy(
282 _ type: Type,
283 uniqueID: DeFiActions.UniqueIdentifier,
284 withFunds: @{FungibleToken.Vault}
285 ): @{FlowYieldVaults.Strategy} {
286 let collateralType = withFunds.getType()
287 let strategyConfig = self.config[type]
288 ?? panic("Could not find a config for Strategy \(type.identifier)")
289 let collateralConfig = strategyConfig[collateralType]
290 ?? panic("Could not find config for collateral \(collateralType.identifier) when creating Strategy \(type.identifier)")
291
292 // Get config values
293 let yieldTokenEVMAddress = collateralConfig["yieldTokenEVMAddress"] as? EVM.EVMAddress
294 ?? panic("Could not find \"yieldTokenEVMAddress\" in config")
295 let swapFeeTier = collateralConfig["swapFeeTier"] as? UInt32
296 ?? panic("Could not find \"swapFeeTier\" in config")
297
298 // Get underlying asset EVM address from the deposited funds type
299 let underlyingAssetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: collateralType)
300 ?? panic("Could not get EVM address for collateral type \(collateralType.identifier)")
301
302 // assign yield token type from the tauUSDF ERC4626 vault address
303 let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress)
304 ?? panic("Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())")
305
306 // create the oracle for the assets to be held in the AutoBalancer retrieving the NAV of the 4626 vault
307 let yieldTokenOracle = ERC4626PriceOracles.PriceOracle(
308 vault: yieldTokenEVMAddress,
309 asset: collateralType,
310 uniqueID: uniqueID
311 )
312
313 // configure and AutoBalancer for this stack
314 let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer(
315 oracle: yieldTokenOracle, // used to determine value of deposits & when to rebalance
316 vaultType: yieldTokenType, // the type of Vault held by the AutoBalancer
317 lowerThreshold: 0.95, // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits
318 upperThreshold: 1.05, // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits
319 rebalanceSink: nil, // nil on init - will be set once a PositionSink is available
320 rebalanceSource: nil, // nil on init - not set for Strategy
321 recurringConfig: nil, // disables native AutoBalancer self-scheduling, no rebalancing required after init
322 uniqueID: uniqueID // identifies AutoBalancer as part of this Strategy
323 )
324 // enables deposits of YieldToken to the AutoBalancer
325 let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
326 // enables withdrawals of YieldToken from the AutoBalancer
327 let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Source from AutoBalancer with id \(uniqueID.id)")
328
329 // create Collateral <-> YieldToken swappers
330 //
331 // Collateral -> YieldToken - can swap via two primary routes:
332 // - via AMM swap pairing Collateral <-> YieldToken
333 // - via ERC4626 vault deposit
334 // Collateral -> YieldToken high-level Swapper contains:
335 // - MultiSwapper aggregates across two sub-swappers
336 // - Collateral -> YieldToken (UniV3 Swapper)
337 // - Collateral -> YieldToken (ERC4626 Swapper)
338 let collateralToYieldAMMSwapper = UniswapV3SwapConnectors.Swapper(
339 factoryAddress: PMStrategiesV1.univ3FactoryEVMAddress,
340 routerAddress: PMStrategiesV1.univ3RouterEVMAddress,
341 quoterAddress: PMStrategiesV1.univ3QuoterEVMAddress,
342 tokenPath: [underlyingAssetEVMAddress, yieldTokenEVMAddress],
343 feePath: [swapFeeTier],
344 inVault: collateralType,
345 outVault: yieldTokenType,
346 coaCapability: PMStrategiesV1._getCOACapability(),
347 uniqueID: uniqueID
348 )
349 // Swap Collateral -> YieldToken via ERC4626 Vault
350 // Morpho vaults use MorphoERC4626SwapConnectors; standard ERC4626 vaults use ERC4626SwapConnectors
351 var collateralToYieldSwapper: SwapConnectors.MultiSwapper? = nil
352 if type == Type<@FUSDEVStrategy>() {
353 let collateralToYieldMorphoERC4626Swapper = MorphoERC4626SwapConnectors.Swapper(
354 vaultEVMAddress: yieldTokenEVMAddress,
355 coa: PMStrategiesV1._getCOACapability(),
356 feeSource: PMStrategiesV1._createFeeSource(withID: uniqueID),
357 uniqueID: uniqueID,
358 isReversed: false
359 )
360 collateralToYieldSwapper = SwapConnectors.MultiSwapper(
361 inVault: collateralType,
362 outVault: yieldTokenType,
363 swappers: [collateralToYieldAMMSwapper, collateralToYieldMorphoERC4626Swapper],
364 uniqueID: uniqueID
365 )
366 } else {
367 let collateralToYieldERC4626Swapper = ERC4626SwapConnectors.Swapper(
368 asset: collateralType,
369 vault: yieldTokenEVMAddress,
370 coa: PMStrategiesV1._getCOACapability(),
371 feeSource: PMStrategiesV1._createFeeSource(withID: uniqueID),
372 uniqueID: uniqueID
373 )
374 collateralToYieldSwapper = SwapConnectors.MultiSwapper(
375 inVault: collateralType,
376 outVault: yieldTokenType,
377 swappers: [collateralToYieldAMMSwapper, collateralToYieldERC4626Swapper],
378 uniqueID: uniqueID
379 )
380 }
381
382 // create YieldToken <-> Collateral swappers
383 //
384 // YieldToken -> Collateral - can swap via two primary routes:
385 // - via AMM swap pairing YieldToken <-> Collateral
386 // - via ERC4626 vault deposit
387 // YieldToken -> Collateral high-level Swapper contains:
388 // - MultiSwapper aggregates across two sub-swappers
389 // - YieldToken -> Collateral (UniV3 Swapper)
390 // - YieldToken -> Collateral (ERC4626 Swapper)
391 let yieldToCollateralAMMSwapper = UniswapV3SwapConnectors.Swapper(
392 factoryAddress: PMStrategiesV1.univ3FactoryEVMAddress,
393 routerAddress: PMStrategiesV1.univ3RouterEVMAddress,
394 quoterAddress: PMStrategiesV1.univ3QuoterEVMAddress,
395 tokenPath: [yieldTokenEVMAddress, underlyingAssetEVMAddress],
396 feePath: [swapFeeTier],
397 inVault: yieldTokenType,
398 outVault: collateralType,
399 coaCapability: PMStrategiesV1._getCOACapability(),
400 uniqueID: uniqueID
401 )
402
403 // Reverse path: YieldToken -> Collateral
404 // Morpho vaults support direct redeem; standard ERC4626 vaults use AMM-only path
405 var yieldToCollateralSwapper: SwapConnectors.MultiSwapper? = nil
406 if type == Type<@FUSDEVStrategy>() {
407 let yieldToCollateralMorphoERC4626Swapper = MorphoERC4626SwapConnectors.Swapper(
408 vaultEVMAddress: yieldTokenEVMAddress,
409 coa: PMStrategiesV1._getCOACapability(),
410 feeSource: PMStrategiesV1._createFeeSource(withID: uniqueID),
411 uniqueID: uniqueID,
412 isReversed: true
413 )
414 yieldToCollateralSwapper = SwapConnectors.MultiSwapper(
415 inVault: yieldTokenType,
416 outVault: collateralType,
417 swappers: [yieldToCollateralAMMSwapper, yieldToCollateralMorphoERC4626Swapper],
418 uniqueID: uniqueID
419 )
420 } else {
421 // Standard ERC4626: AMM-only reverse (no synchronous redeem support)
422 yieldToCollateralSwapper = SwapConnectors.MultiSwapper(
423 inVault: yieldTokenType,
424 outVault: collateralType,
425 swappers: [yieldToCollateralAMMSwapper],
426 uniqueID: uniqueID
427 )
428 }
429
430 // init SwapSink directing swapped funds to AutoBalancer
431 //
432 // Swaps provided Collateral to YieldToken & deposits to the AutoBalancer
433 let abaSwapSink = SwapConnectors.SwapSink(swapper: collateralToYieldSwapper!, sink: abaSink, uniqueID: uniqueID)
434 // Swaps YieldToken & provides swapped Collateral, sourcing YieldToken from the AutoBalancer
435 let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToCollateralSwapper!, source: abaSource, uniqueID: uniqueID)
436
437 abaSwapSink.depositCapacity(from: &withFunds as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
438
439 assert(withFunds.balance == 0.0, message: "Vault should be empty after depositing")
440 destroy withFunds
441
442 // Use the same uniqueID passed to createStrategy so Strategy.burnCallback
443 // calls _cleanupAutoBalancer with the correct ID
444 switch type {
445 case Type<@syWFLOWvStrategy>():
446 return <-create syWFLOWvStrategy(id: uniqueID, sink: abaSwapSink, source: abaSwapSource)
447 case Type<@tauUSDFvStrategy>():
448 return <-create tauUSDFvStrategy(id: uniqueID, sink: abaSwapSink, source: abaSwapSource)
449 case Type<@FUSDEVStrategy>():
450 return <-create FUSDEVStrategy(id: uniqueID, sink: abaSwapSink, source: abaSwapSource)
451 default:
452 panic("Unsupported strategy type \(type.identifier)")
453 }
454 }
455 }
456
457 access(all) entitlement Configure
458
459 /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which
460 /// may utilize resource consumption (i.e. account storage). Since Strategy creation consumes account storage
461 /// via configured AutoBalancers
462 access(all) resource StrategyComposerIssuer : FlowYieldVaults.StrategyComposerIssuer {
463 /// { StrategyComposer Type: { Strategy Type: { Collateral Type: { String: AnyStruct } } } }
464 access(all) let configs: {Type: {Type: {Type: {String: AnyStruct}}}}
465
466 init(configs: {Type: {Type: {Type: {String: AnyStruct}}}}) {
467 self.configs = configs
468 }
469
470 access(all) view fun getSupportedComposers(): {Type: Bool} {
471 return {
472 Type<@ERC4626VaultStrategyComposer>(): true
473 }
474 }
475 access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} {
476 pre {
477 self.getSupportedComposers()[type] == true:
478 "Unsupported StrategyComposer \(type.identifier) requested"
479 (&self.configs[type] as &{Type: {Type: {String: AnyStruct}}}?) != nil:
480 "Could not find config for StrategyComposer \(type.identifier)"
481 }
482 switch type {
483 case Type<@ERC4626VaultStrategyComposer>():
484 return <- create ERC4626VaultStrategyComposer(self.configs[type]!)
485 default:
486 panic("Unsupported StrategyComposer \(type.identifier) requested")
487 }
488 }
489 access(Configure) fun upsertConfigFor(composer: Type, config: {Type: {Type: {String: AnyStruct}}}) {
490 pre {
491 self.getSupportedComposers()[composer] == true:
492 "Unsupported StrategyComposer Type \(composer.identifier)"
493 }
494 // Validate keys
495 for stratType in config.keys {
496 assert(stratType.isSubtype(of: Type<@{FlowYieldVaults.Strategy}>()),
497 message: "Invalid config key \(stratType.identifier) - not a FlowYieldVaults.Strategy Type")
498 for collateralType in config[stratType]!.keys {
499 assert(collateralType.isSubtype(of: Type<@{FungibleToken.Vault}>()),
500 message: "Invalid config key at config[\(stratType.identifier)] - \(collateralType.identifier) is not a FungibleToken.Vault")
501 }
502 }
503 // Merge instead of overwrite
504 let existingComposerConfig = self.configs[composer] ?? {}
505 var mergedComposerConfig: {Type: {Type: {String: AnyStruct}}} = existingComposerConfig
506
507 for stratType in config.keys {
508 let newPerCollateral = config[stratType]!
509 let existingPerCollateral = mergedComposerConfig[stratType] ?? {}
510 var mergedPerCollateral: {Type: {String: AnyStruct}} = existingPerCollateral
511
512 for collateralType in newPerCollateral.keys {
513 mergedPerCollateral[collateralType] = newPerCollateral[collateralType]!
514 }
515 mergedComposerConfig[stratType] = mergedPerCollateral
516 }
517
518 self.configs[composer] = mergedComposerConfig
519 }
520 }
521
522 /// Returns the COA capability for this account
523 /// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors
524 access(self)
525 fun _getCOACapability(): Capability<auth(EVM.Call, EVM.Bridge, EVM.Owner) &EVM.CadenceOwnedAccount> {
526 let coaCap = self.account.capabilities.storage.issue<auth(EVM.Call, EVM.Bridge, EVM.Owner) &EVM.CadenceOwnedAccount>(/storage/evm)
527 assert(coaCap.check(), message: "Could not issue COA capability")
528 return coaCap
529 }
530
531 /// Returns a FungibleTokenConnectors.VaultSinkAndSource used to subsidize cross VM token movement in contract-
532 /// defined strategies.
533 access(self)
534 fun _createFeeSource(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} {
535 let capPath = /storage/strategiesFeeSource
536 if self.account.storage.type(at: capPath) == nil {
537 let cap = self.account.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(/storage/flowTokenVault)
538 self.account.storage.save(cap, to: capPath)
539 }
540 let vaultCap = self.account.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>>(from: capPath)
541 ?? panic("Could not find fee source Capability at \(capPath)")
542 return FungibleTokenConnectors.VaultSinkAndSource(
543 min: nil,
544 max: nil,
545 vault: vaultCap,
546 uniqueID: withID
547 )
548 }
549
550 init(
551 univ3FactoryEVMAddress: String,
552 univ3RouterEVMAddress: String,
553 univ3QuoterEVMAddress: String
554 ) {
555 self.univ3FactoryEVMAddress = EVM.addressFromString(univ3FactoryEVMAddress)
556 self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress)
557 self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress)
558 self.IssuerStoragePath = StoragePath(identifier: "PMStrategiesV1ComposerIssuer_\(self.account.address)")!
559 self.config = {}
560
561 // Start with empty configs - strategy configs are added via upsertConfigFor admin transactions
562 let configs: {Type: {Type: {Type: {String: AnyStruct}}}} = {
563 Type<@ERC4626VaultStrategyComposer>(): {}
564 }
565 self.account.storage.save(<-create StrategyComposerIssuer(configs: configs), to: self.IssuerStoragePath)
566
567 // TODO: this is temporary until we have a better way to pass user's COAs to inner connectors
568 // create a COA in this account
569 if self.account.storage.type(at: /storage/evm) == nil {
570 self.account.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm)
571 let cap = self.account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(/storage/evm)
572 self.account.capabilities.publish(cap, at: /public/evm)
573 }
574 }
575}
576