From a978f9b341a1e1bf58ed60c58e622ac628d89ccf Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:25:29 -0500 Subject: [PATCH 01/31] strategy --- .../contracts/FlowYieldVaultsStrategies.cdc | 30 + .../contracts/FlowYieldVaultsStrategiesV1.cdc | 580 ++++++++++++++++++ 2 files changed, 610 insertions(+) create mode 100644 cadence/contracts/FlowYieldVaultsStrategiesV1.cdc diff --git a/cadence/contracts/FlowYieldVaultsStrategies.cdc b/cadence/contracts/FlowYieldVaultsStrategies.cdc index 97542f5..650c34d 100644 --- a/cadence/contracts/FlowYieldVaultsStrategies.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategies.cdc @@ -533,6 +533,36 @@ access(all) contract FlowYieldVaultsStrategies { access(all) entitlement Configure + access(self) + fun makeCollateralConfig( + yieldTokenEVMAddress: EVM.EVMAddress, + univ3FactoryEVMAddress: EVM.EVMAddress, + univ3RouterEVMAddress: EVM.EVMAddress, + univ3QuoterEVMAddress: EVM.EVMAddress, + yieldToCollateralAddressPath: [EVM.EVMAddress], + yieldToCollateralFeePath: [UInt32] + ): {String: AnyStruct} { + pre { + yieldToCollateralAddressPath.length > 1: + "Invalid Uniswap V3 swap path length" + yieldToCollateralFeePath.length == yieldToCollateralAddressPath.length - 1: + "Uniswap V3 fee path length must be path length - 1" + yieldToCollateralAddressPath[0].equals(yieldTokenEVMAddress): + "UniswapV3 swap path must start with yield token" + } + + return { + "univ3FactoryEVMAddress": univ3FactoryEVMAddress, + "univ3RouterEVMAddress": univ3RouterEVMAddress, + "univ3QuoterEVMAddress": univ3QuoterEVMAddress, + "yieldTokenEVMAddress": yieldTokenEVMAddress, + "yieldToCollateralUniV3AddressPaths": { + // we’ll store with the collateral vault type as key later + } as {Type: [EVM.EVMAddress]}, + "yieldToCollateralUniV3FeePaths": { + } as {Type: [UInt32]} + } + } /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which /// may utilize resource consumption (i.e. account storage). Since TracerStrategy creation consumes account storage /// via configured AutoBalancers diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc new file mode 100644 index 0000000..c8dc369 --- /dev/null +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc @@ -0,0 +1,580 @@ +// standards +import "FungibleToken" +import "FlowToken" +import "EVM" +// DeFiActions +import "DeFiActionsUtils" +import "DeFiActions" +import "SwapConnectors" +import "FungibleTokenConnectors" +// amm integration +import "UniswapV3SwapConnectors" +import "ERC4626SwapConnectors" +import "ERC4626Utils" +// Lending protocol +import "FlowCreditMarket" +// FlowYieldVaults platform +import "FlowYieldVaultsClosedBeta" +import "FlowYieldVaults" +import "FlowYieldVaultsAutoBalancers" +// scheduler +import "FlowTransactionScheduler" +import "FlowYieldVaultsSchedulerRegistry" +// tokens +import "YieldToken" +import "MOET" +// vm bridge +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +// live oracles +import "ERC4626PriceOracles" + +/// FlowYieldVaultsStrategiesV1 +/// +/// This contract defines Strategies used in the FlowYieldVaults platform. +/// +/// A Strategy instance can be thought of as objects wrapping a stack of DeFiActions connectors wired together to +/// (optimally) generate some yield on initial deposits. Strategies can be simple such as swapping into a yield-bearing +/// asset (such as stFLOW) or more complex DeFiActions stacks. +/// +/// A StrategyComposer is tasked with the creation of a supported Strategy. It's within the stacking of DeFiActions +/// connectors that the true power of the components lies. +/// +access(all) contract FlowYieldVaultsStrategiesV1 { + + access(all) let univ3FactoryEVMAddress: EVM.EVMAddress + access(all) let univ3RouterEVMAddress: EVM.EVMAddress + access(all) let univ3QuoterEVMAddress: EVM.EVMAddress + + /// Canonical StoragePath where the StrategyComposerIssuer should be stored + access(all) let IssuerStoragePath: StoragePath + + /// This strategy uses mUSDF vaults + access(all) resource mUSDFStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource { + /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol- + /// specific Identifier to associated connectors on construction + access(contract) var uniqueID: DeFiActions.UniqueIdentifier? + access(self) let position: FlowCreditMarket.Position + access(self) var sink: {DeFiActions.Sink} + access(self) var source: {DeFiActions.Source} + + init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowCreditMarket.Position) { + self.uniqueID = id + self.position = position + self.sink = position.createSink(type: collateralType) + self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) + } + + // Inherited from FlowYieldVaults.Strategy default implementation + // access(all) view fun isSupportedCollateralType(_ type: Type): Bool + + access(all) view fun getSupportedCollateralTypes(): {Type: Bool} { + return { self.sink.getSinkType(): true } + } + /// Returns the amount available for withdrawal via the inner Source + access(all) fun availableBalance(ofToken: Type): UFix64 { + return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0 + } + /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference + access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) { + self.sink.depositCapacity(from: from) + } + /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported, + /// an empty Vault is returned. + access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} { + if ofToken != self.source.getSourceType() { + return <- DeFiActionsUtils.getEmptyVault(ofToken) + } + return <- self.source.withdrawAvailable(maxAmount: maxAmount) + } + /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer + access(contract) fun burnCallback() { + FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!) + } + access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { + return DeFiActions.ComponentInfo( + type: self.getType(), + id: self.id(), + innerComponents: [ + self.sink.getComponentInfo(), + self.source.getComponentInfo() + ] + ) + } + access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? { + return self.uniqueID + } + access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) { + self.uniqueID = id + } + } + + /// This StrategyComposer builds a mUSDFStrategy + access(all) resource mUSDFStrategyComposer : FlowYieldVaults.StrategyComposer { + /// { Strategy Type: { Collateral Type: { String: AnyStruct } } } + access(self) let config: {Type: {Type: {String: AnyStruct}}} + + init(_ config: {Type: {Type: {String: AnyStruct}}}) { + self.config = config + } + + /// Returns the Types of Strategies composed by this StrategyComposer + access(all) view fun getComposedStrategyTypes(): {Type: Bool} { + let composed: {Type: Bool} = {} + for t in self.config.keys { + composed[t] = true + } + return composed + } + + /// Returns the Vault types which can be used to initialize a given Strategy + access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} { + let supported: {Type: Bool} = {} + if let strategyConfig = &self.config[forStrategy] as &{Type: {String: AnyStruct}}? { + for collateralType in strategyConfig.keys { + supported[collateralType] = true + } + } + return supported + } + + /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the + /// provided Vault type + access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} { + let supportedInitVaults = self.getSupportedInitializationVaults(forStrategy: forStrategy) + if supportedInitVaults[initializedWith] == true { + return { initializedWith: true } + } + return {} + } + + /// Composes a Strategy of the given type with the provided funds + access(all) fun createStrategy( + _ type: Type, + uniqueID: DeFiActions.UniqueIdentifier, + withFunds: @{FungibleToken.Vault} + ): @{FlowYieldVaults.Strategy} { + let collateralType = withFunds.getType() + let strategyConfig = self.config[type] + ?? panic("Could not find a config for Strategy \(type.identifier) initialized with \(collateralType.identifier)") + let collateralConfig = strategyConfig[collateralType] + ?? panic("Could not find config for collateral \(collateralType.identifier) when creating Strategy \(type.identifier)") + + // assign token types & associated EVM Addresses + let moetTokenType: Type = Type<@MOET.Vault>() + let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType) + ?? panic("Token Vault type \(moetTokenType.identifier) has not yet been registered with the VMbridge") + let yieldTokenEVMAddress = collateralConfig["yieldTokenEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"yieldTokenEVMAddress\" in config") + let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress) + ?? panic("Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())") + + // assign underlying asset EVM address & type - assumed to be stablecoin for the strategy + let underlying4626AssetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress( + vault: yieldTokenEVMAddress + ) ?? panic("Could not get the underlying asset's EVM address for ERC4626Vault \(yieldTokenEVMAddress.toString())") + let underlying4626AssetType = FlowEVMBridgeConfig.getTypeAssociated(with: underlying4626AssetEVMAddress) + ?? panic("Could not retrieve the VM Bridge associated Type for the ERC4626 underlying asset \(underlying4626AssetEVMAddress.toString())") + + // create the oracle for the assets to be held in the AutoBalancer retrieving the NAV of the 4626 vault + let yieldTokenOracle = ERC4626PriceOracles.PriceOracle( + vault: yieldTokenEVMAddress, + asset: underlying4626AssetType, + // asset: moetTokenType, // TODO: make a composite oracle that returns the price denominated in MOET + uniqueID: uniqueID + ) + + // Create recurring config for automatic rebalancing + let recurringConfig = FlowYieldVaultsStrategiesV1._createRecurringConfig(withID: uniqueID) + + // configure and AutoBalancer for this stack with native recurring scheduling + let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer( + oracle: yieldTokenOracle, // used to determine value of deposits & when to rebalance + vaultType: yieldTokenType, // the type of Vault held by the AutoBalancer + lowerThreshold: 0.95, // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits + upperThreshold: 1.05, // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits + rebalanceSink: nil, // nil on init - will be set once a PositionSink is available + rebalanceSource: nil, // nil on init - not set for Strategy + recurringConfig: recurringConfig, // enables native AutoBalancer self-scheduling + uniqueID: uniqueID // identifies AutoBalancer as part of this Strategy + ) + // enables deposits of YieldToken to the AutoBalancer + let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") + // enables withdrawals of YieldToken from the AutoBalancer + let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") + + // create MOET <-> YIELD swappers + // + // MOET -> YIELD - MOET can swap to YieldToken via two primary routes + // - via AMM swap pairing MOET <-> YIELD + // - via 4626 vault, swapping first to underlying asset then depositing to the 4626 vault + // MOET -> YIELD high-level Swapper then contains + // - MultiSwapper aggregates across two sub-swappers + // - MOET -> YIELD (UniV3 Swapper) + // - SequentialSwapper + // - MOET -> UNDERLYING (UniV3 Swapper) + // - UNDERLYING -> YIELD (ERC4626Swapper) + let moetToYieldAMMSwapper = UniswapV3SwapConnectors.Swapper( + factoryAddress: self.univ3FactoryEVMAddress, + routerAddress: self.univ3RouterEVMAddress, + quoterAddress: self.univ3QuoterEVMAddress, + tokenPath: [moetTokenEVMAddress, yieldTokenEVMAddress], + feePath: [100], + inVault: moetTokenType, + outVault: yieldTokenType, + coaCapability: FlowYieldVaultsStrategiesV1._getCOACapability(), + uniqueID: uniqueID + ) + // Swap MOET -> UNDERLYING via AMM + let moetToUnderlyingAssetSwapper = UniswapV3SwapConnectors.Swapper( + factoryAddress: self.univ3FactoryEVMAddress, + routerAddress: self.univ3RouterEVMAddress, + quoterAddress: self.univ3QuoterEVMAddress, + tokenPath: [moetTokenEVMAddress, underlying4626AssetEVMAddress], + feePath: [100], + inVault: moetTokenType, + outVault: underlying4626AssetType, + coaCapability: FlowYieldVaultsStrategiesV1._getCOACapability(), + uniqueID: uniqueID + ) + // Swap UNDERLYING -> YIELD via ERC4626 Vault + let underlyingTo4626Swapper = ERC4626SwapConnectors.Swapper( + asset: underlying4626AssetType, + vault: yieldTokenEVMAddress, + coa: FlowYieldVaultsStrategiesV1._getCOACapability(), + feeSource: FlowYieldVaultsStrategiesV1._createFeeSource(withID: uniqueID), + uniqueID: uniqueID + ) + // Compose v3 swapper & 4626 swapper into sequential swapper for MOET -> UNDERLYING -> YIELD + let moetToYieldSeqSwapper = SwapConnectors.SequentialSwapper( + swappers: [moetToUnderlyingAssetSwapper, underlyingTo4626Swapper], + uniqueID: uniqueID + ) + // Finally, add the two MOET -> YIELD swappers into an aggregate MultiSwapper + let moetToYieldSwapper = SwapConnectors.MultiSwapper( + inVault: moetTokenType, + outVault: yieldTokenType, + swappers: [moetToYieldAMMSwapper, moetToYieldSeqSwapper], + uniqueID: uniqueID + ) + + // YIELD -> MOET + // - Targets the MOET <-> YIELD pool as the only route since withdraws from the ERC4626 Vault are async + let yieldToMOETSwapper = UniswapV3SwapConnectors.Swapper( + factoryAddress: self.univ3FactoryEVMAddress, + routerAddress: self.univ3RouterEVMAddress, + quoterAddress: self.univ3QuoterEVMAddress, + tokenPath: [yieldTokenEVMAddress, moetTokenEVMAddress], + feePath: [100], + inVault: yieldTokenType, + outVault: moetTokenType, + coaCapability: FlowYieldVaultsStrategiesV1._getCOACapability(), + uniqueID: uniqueID + ) + + // init SwapSink directing swapped funds to AutoBalancer + // + // Swaps provided MOET to YIELD & deposits to the AutoBalancer + let abaSwapSink = SwapConnectors.SwapSink(swapper: moetToYieldSwapper, sink: abaSink, uniqueID: uniqueID) + // Swaps YIELD & provides swapped MOET, sourcing YIELD from the AutoBalancer + let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToMOETSwapper, source: abaSource, uniqueID: uniqueID) + + // open a FlowCreditMarket position + let poolCap = FlowYieldVaultsStrategiesV1.account.storage.copy>( + from: FlowCreditMarket.PoolCapStoragePath + ) ?? panic("Missing or invalid pool capability") + let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap") + + let pid = poolRef.createPosition( + funds: <-withFunds, + issuanceSink: abaSwapSink, + repaymentSource: abaSwapSource, + pushToDrawDownSink: true + ) + let position = FlowCreditMarket.Position(id: pid, pool: poolCap) + + // get Sink & Source connectors relating to the new Position + let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true) + let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) + + // init YieldToken -> Collateral Swapper + // + // get UniswapV3 path configs + let collateralUniV3AddressPathConfig = collateralConfig["yieldToCollateralUniV3AddressPaths"] as? {Type: [EVM.EVMAddress]} + ?? panic("Could not find UniswapV3 address paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)") + let uniV3AddressPath = collateralUniV3AddressPathConfig[collateralType] + ?? panic("Could not find UniswapV3 address path for collateral type \(collateralType.identifier)") + assert(uniV3AddressPath.length > 1, message: "Invalid Uniswap V3 swap path length of \(uniV3AddressPath.length)") + assert(uniV3AddressPath[0].equals(yieldTokenEVMAddress), + message: "UniswapV3 swap path does not match - expected path[0] to be \(yieldTokenEVMAddress.toString()) but found \(uniV3AddressPath[0].toString())") + let collateralUniV3FeePathConfig = collateralConfig["yieldToCollateralUniV3FeePaths"] as? {Type: [UInt32]} + ?? panic("Could not find UniswapV3 fee paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)") + let uniV3FeePath = collateralUniV3FeePathConfig[collateralType] + ?? panic("Could not find UniswapV3 fee path for collateral type \(collateralType.identifier)") + assert(uniV3FeePath.length > 0, message: "Invalid Uniswap V3 fee path length of \(uniV3FeePath.length)") + // initialize the swapper used for recollateralization of the lending position as YIELD increases in value + let yieldToFlowSwapper = UniswapV3SwapConnectors.Swapper( + factoryAddress: self.univ3FactoryEVMAddress, + routerAddress: univ3RouterEVMAddress, + quoterAddress: univ3QuoterEVMAddress, + tokenPath: uniV3AddressPath, + feePath: uniV3FeePath, + inVault: yieldTokenType, + outVault: collateralType, + coaCapability: FlowYieldVaultsStrategiesV1._getCOACapability(), + uniqueID: uniqueID + ) + // allows for YIELD to be deposited to the Position as the collateral basis + let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID) + + // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value, recollateralizing + // the position + autoBalancer.setSink(positionSwapSink, updateSinkID: true) + + // Use the same uniqueID passed to createStrategy so Strategy.burnCallback + // calls _cleanupAutoBalancer with the correct ID + return <-create mUSDFStrategy( + id: uniqueID, + collateralType: collateralType, + position: position + ) + } + } + + access(all) entitlement Configure + + access(self) + fun makeCollateralConfig( + yieldTokenEVMAddress: EVM.EVMAddress, + yieldToCollateralAddressPath: [EVM.EVMAddress], + yieldToCollateralFeePath: [UInt32] + ): {String: AnyStruct} { + pre { + yieldToCollateralAddressPath.length > 1: + "Invalid Uniswap V3 swap path length" + yieldToCollateralFeePath.length == yieldToCollateralAddressPath.length - 1: + "Uniswap V3 fee path length must be path length - 1" + yieldToCollateralAddressPath[0].equals(yieldTokenEVMAddress): + "UniswapV3 swap path must start with yield token" + } + + return { + "univ3FactoryEVMAddress": self.univ3FactoryEVMAddress, + "univ3RouterEVMAddress": self.univ3RouterEVMAddress, + "univ3QuoterEVMAddress": self.univ3QuoterEVMAddress, + "yieldTokenEVMAddress": yieldTokenEVMAddress, + "yieldToCollateralUniV3AddressPaths": { + // we’ll store with the collateral vault type as key later + } as {Type: [EVM.EVMAddress]}, + "yieldToCollateralUniV3FeePaths": { + } as {Type: [UInt32]} + } + } + /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which + /// may utilize resource consumption (i.e. account storage). Since Strategy creation consumes account storage + /// via configured AutoBalancers + access(all) resource StrategyComposerIssuer : FlowYieldVaults.StrategyComposerIssuer { + /// { StrategyComposer Type: { Strategy Type: { Collateral Type: { String: AnyStruct } } } } + access(all) let configs: {Type: {Type: {Type: {String: AnyStruct}}}} + + init(configs: {Type: {Type: {Type: {String: AnyStruct}}}}) { + self.configs = configs + } + + access(all) view fun getSupportedComposers(): {Type: Bool} { + return { + Type<@mUSDFStrategyComposer>(): true, + } + } + access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} { + pre { + self.getSupportedComposers()[type] == true: + "Unsupported StrategyComposer \(type.identifier) requested" + (&self.configs[type] as &{Type: {Type: {String: AnyStruct}}}?) != nil: + "Could not find config for StrategyComposer \(type.identifier)" + } + switch type { + case Type<@mUSDFStrategyComposer>(): + return <- create mUSDFStrategyComposer(self.configs[type]!) + default: + panic("Unsupported StrategyComposer \(type.identifier) requested") + } + } + + access(Configure) + fun upsertConfigFor( + composer: Type, + config: {Type: {Type: {String: AnyStruct}}} + ) { + pre { + self.getSupportedComposers()[composer] == true: + "Unsupported StrategyComposer Type \(composer.identifier)" + } + + // Validate keys + for stratType in config.keys { + assert(stratType.isSubtype(of: Type<@{FlowYieldVaults.Strategy}>()), + message: "Invalid config key \(stratType.identifier) - not a FlowYieldVaults.Strategy Type") + for collateralType in config[stratType]!.keys { + assert(collateralType.isSubtype(of: Type<@{FungibleToken.Vault}>()), + message: "Invalid config key at config[\(stratType.identifier)] - \(collateralType.identifier) is not a FungibleToken.Vault") + } + } + + // Merge instead of overwrite + let existingComposerConfig = self.configs[composer] ?? {} + var mergedComposerConfig: {Type: {Type: {String: AnyStruct}}} = existingComposerConfig + + for stratType in config.keys { + let newPerCollateral = config[stratType]! + let existingPerCollateral = mergedComposerConfig[stratType] ?? {} + var mergedPerCollateral: {Type: {String: AnyStruct}} = existingPerCollateral + + for collateralType in newPerCollateral.keys { + mergedPerCollateral[collateralType] = newPerCollateral[collateralType]! + } + mergedComposerConfig[stratType] = mergedPerCollateral + } + + self.configs[composer] = mergedComposerConfig + } + + access(Configure) fun addOrUpdateCollateralConfig( + composer: Type, + strategyType: Type, + collateralVaultType: Type, + yieldTokenEVMAddress: EVM.EVMAddress, + yieldToCollateralAddressPath: [EVM.EVMAddress], + yieldToCollateralFeePath: [UInt32] + ) { + pre { + self.getSupportedComposers()[composer] == true: + "Unsupported StrategyComposer Type \(composer.identifier)" + strategyType.isSubtype(of: Type<@{FlowYieldVaults.Strategy}>()): + "Strategy type \(strategyType.identifier) is not a FlowYieldVaults.Strategy" + collateralVaultType.isSubtype(of: Type<@{FungibleToken.Vault}>()): + "Collateral type \(collateralVaultType.identifier) is not a FungibleToken.Vault" + } + + // Base struct with shared addresses + var base = FlowYieldVaultsStrategiesV1.makeCollateralConfig( + yieldTokenEVMAddress: yieldTokenEVMAddress, + yieldToCollateralAddressPath: yieldToCollateralAddressPath, + yieldToCollateralFeePath: yieldToCollateralFeePath + ) + + // Plug in the type-keyed maps + let addressPaths = { + collateralVaultType: yieldToCollateralAddressPath + } as {Type: [EVM.EVMAddress]} + + let feePaths = { + collateralVaultType: yieldToCollateralFeePath + } as {Type: [UInt32]} + + base["yieldToCollateralUniV3AddressPaths"] = addressPaths + base["yieldToCollateralUniV3FeePaths"] = feePaths + + // Wrap into the nested config expected by upsertConfigFor + let singleCollateralConfig: {Type: {Type: {String: AnyStruct}}} = { + strategyType: { + collateralVaultType: base + } + } + + self.upsertConfigFor(composer: composer, config: singleCollateralConfig) + } + } + + /// Returns the COA capability for this account + /// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors + access(self) + fun _getCOACapability(): Capability { + let coaCap = self.account.capabilities.storage.issue(/storage/evm) + assert(coaCap.check(), message: "Could not issue COA capability") + return coaCap + } + + /// Returns a FungibleTokenConnectors.VaultSinkAndSource used to subsidize cross VM token movement in contract- + /// defined strategies. + access(self) + fun _createFeeSource(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} { + let capPath = /storage/strategiesFeeSource + if self.account.storage.type(at: capPath) == nil { + let cap = self.account.capabilities.storage.issue(/storage/flowTokenVault) + self.account.storage.save(cap, to: capPath) + } + let vaultCap = self.account.storage.copy>(from: capPath) + ?? panic("Could not find fee source Capability at \(capPath)") + return FungibleTokenConnectors.VaultSinkAndSource( + min: nil, + max: nil, + vault: vaultCap, + uniqueID: withID + ) + } + + /// Creates an AutoBalancerRecurringConfig for scheduled rebalancing. + /// The txnFunder uses the contract's FlowToken vault to pay for scheduling fees. + access(self) + fun _createRecurringConfig(withID: DeFiActions.UniqueIdentifier?): DeFiActions.AutoBalancerRecurringConfig { + // Create txnFunder that can provide/accept FLOW for scheduling fees + let txnFunder = self._createTxnFunder(withID: withID) + + return DeFiActions.AutoBalancerRecurringConfig( + interval: 60, // Rebalance every 60 seconds + priority: FlowTransactionScheduler.Priority.Medium, + executionEffort: 800, + forceRebalance: false, + txnFunder: txnFunder + ) + } + + /// Creates a Sink+Source for the AutoBalancer to use for scheduling fees + access(self) + fun _createTxnFunder(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} { + let capPath = /storage/autoBalancerTxnFunder + if self.account.storage.type(at: capPath) == nil { + let cap = self.account.capabilities.storage.issue(/storage/flowTokenVault) + self.account.storage.save(cap, to: capPath) + } + let vaultCap = self.account.storage.copy>(from: capPath) + ?? panic("Could not find txnFunder Capability at \(capPath)") + return FungibleTokenConnectors.VaultSinkAndSource( + min: nil, + max: nil, + vault: vaultCap, + uniqueID: withID + ) + } + + init( + univ3FactoryEVMAddress: String, + univ3RouterEVMAddress: String, + univ3QuoterEVMAddress: String, + ) { + self.univ3FactoryEVMAddress = EVM.addressFromString(univ3FactoryEVMAddress) + self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress) + self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress) + self.IssuerStoragePath = StoragePath(identifier: "FlowYieldVaultsStrategyV1ComposerIssuer_\(self.account.address)")! + + let moetType = Type<@MOET.Vault>() + let moetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>()) + ?? panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge") + + let configs: {Type: {Type: {Type: {String: AnyStruct}}}} = { + Type<@mUSDFStrategyComposer>(): { + Type<@mUSDFStrategy>(): {} + }, + } + self.account.storage.save(<-create StrategyComposerIssuer(configs: configs), to: self.IssuerStoragePath) + + // TODO: this is temporary until we have a better way to pass user's COAs to inner connectors + // create a COA in this account + if self.account.storage.type(at: /storage/evm) == nil { + self.account.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) + let cap = self.account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(/storage/evm) + self.account.capabilities.publish(cap, at: /public/evm) + } + } +} From 5bb42a8daf079606075410b4070c7ac552c0da5c Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:17:52 -0500 Subject: [PATCH 02/31] switch strategy --- cadence/tests/test_helpers.cdc | 11 ++++++ flow.json | 63 +++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index ca71706..ae76133 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -322,6 +322,17 @@ access(all) fun deployContracts() { ) Test.expect(err, Test.beNil()) + err = Test.deployContract( + name: "FlowYieldVaultsStrategiesV1", + path: "../contracts/FlowYieldVaultsStrategiesV1.cdc", + arguments: [ + "0x986Cb42b0557159431d48fE0A40073296414d410", + "0x92657b195e22b69E4779BBD09Fa3CD46F0CF8e39", + "0x8dd92c8d0C3b304255fF9D98ae59c3385F88360C" + ] + ) + + Test.expect(err, Test.beNil()) // Mocked Strategy err = Test.deployContract( name: "MockStrategy", diff --git a/flow.json b/flow.json index 977b54f..d76f50a 100644 --- a/flow.json +++ b/flow.json @@ -161,6 +161,15 @@ "testnet": "d2580caf2ef07c2f" } }, + "FlowYieldVaultsStrategiesV1": { + "source": "cadence/contracts/FlowYieldVaultsStrategiesV1.cdc", + "aliases": { + "emulator": "045a1763c93006ca", + "mainnet": "b1d63873c3cc9f79", + "testing": "0000000000000009", + "testnet": "d2580caf2ef07c2f" + } + }, "FungibleTokenConnectors": { "source": "./lib/FlowCreditMarket/FlowActions/cadence/contracts/connectors/FungibleTokenConnectors.cdc", "aliases": { @@ -847,6 +856,23 @@ "type": "Array" } ] + }, + { + "name": "FlowYieldVaultsStrategiesV1", + "args": [ + { + "value": "0x986Cb42b0557159431d48fE0A40073296414d410", + "type": "String" + }, + { + "value": "0x92657b195e22b69E4779BBD09Fa3CD46F0CF8e39", + "type": "String" + }, + { + "value": "0x8dd92c8d0C3b304255fF9D98ae59c3385F88360C", + "type": "String" + } + ] } ], "mock-incrementfi": [ @@ -941,8 +967,26 @@ "type": "Array" } ] + }, + { + "name": "FlowYieldVaultsStrategiesV1", + "args": [ + { + "value": "0xca6d7Bb03334bBf135902e1d919a5feccb461632", + "type": "String" + }, + { + "value": "0xeEDC6Ff75e1b10B903D9013c358e446a73d35341", + "type": "String" + }, + { + "value": "0x370A8DF17742867a44e56223EC20D82092242C85", + "type": "String" + } + ] } - ] }, + ] + }, "testnet": { "testnet-admin": [ { @@ -1011,6 +1055,23 @@ "type": "Array" } ] + }, + { + "name": "FlowYieldVaultsStrategiesV1", + "args": [ + { + "value": "0x92657b195e22b69E4779BBD09Fa3CD46F0CF8e39", + "type": "String" + }, + { + "value": "0x2Db6468229F6fB1a77d248Dbb1c386760C257804", + "type": "String" + }, + { + "value": "0xA1e0E4CCACA34a738f03cFB1EAbAb16331FA3E2c", + "type": "String" + } + ] } ] } From cbea6291c5e63fc9537d604063e3444bba179520 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:10:14 -0500 Subject: [PATCH 03/31] setup scripts --- local/setup_emulator.sh | 12 ++++++++++++ local/setup_mainnet.sh | 16 +++++++++++++--- local/setup_testnet.sh | 13 ++++++++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/local/setup_emulator.sh b/local/setup_emulator.sh index f390700..eb552e6 100755 --- a/local/setup_emulator.sh +++ b/local/setup_emulator.sh @@ -44,6 +44,18 @@ flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strate /storage/FlowYieldVaultsStrategyComposerIssuer_0x045a1763c93006ca \ --signer emulator-flow-yield-vaults +# flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ +# "A.0ae53cb6e3f42a79.FlowToken.Vault" \ +# + + + +flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strategy_composer.cdc \ + 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1.USDFStrategy' \ + 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1.USDFStrategyComposer' \ + /storage/FlowYieldVaultsStrategyV1ComposerIssuer_0x045a1763c93006ca \ + --signer emulator-flow-yield-vaults + # grant PoolBeta cap echo "Grant Protocol Beta access to FlowYieldVaults" flow transactions send ./lib/FlowCreditMarket/cadence/tests/transactions/flow-credit-market/pool-management/03_grant_beta.cdc \ diff --git a/local/setup_mainnet.sh b/local/setup_mainnet.sh index 07c2147..da58c35 100755 --- a/local/setup_mainnet.sh +++ b/local/setup_mainnet.sh @@ -57,11 +57,21 @@ flow transactions send ./cadence/transactions/mocks/swapper/set_liquidity_connec flow transactions send ./lib/FlowCreditMarket/FlowActions/cadence/transactions/fungible-tokens/setup_generic_vault.cdc 'A.1e4aa0b87d10b141.EVMVMBridgedToken_c52e820d2d6207d18667a97e2c6ac22eb26e803c.Vault' --network mainnet --signer mainnet-admin # flow transactions send ./cadence/transactions/mocks/swapper/set_liquidity_connector.cdc /storage/EVMVMBridgedToken_4154d5b0e2931a0a1e5b733f19161aa7d2fc4b95Vault --network mainnet --signer mainnet-admin # + +flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ + 'A.1654653399040a61.FlowToken.Vault' \ + "0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c" \ + '["0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c","0x2aaBea2058b5aC2D339b163C6Ab6f2b6d53aabED","0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e"]' \ + '[100,3000]' \ + --network mainnet \ + --signer mainnet-admin + +# # add TracerStrategy as supported Strategy with the ability to initialize when new YieldVaults are created flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strategy_composer.cdc \ - 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategies.mUSDCStrategy' \ - 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategies.mUSDCStrategyComposer' \ - /storage/FlowYieldVaultsStrategyComposerIssuer_0xb1d63873c3cc9f79 \ + 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1.mUSDFStrategy' \ + 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1.mUSDFStrategyComposer' \ + /storage/FlowYieldVaultsStrategyV1ComposerIssuer_0xb1d63873c3cc9f79 \ --network mainnet \ --signer mainnet-admin diff --git a/local/setup_testnet.sh b/local/setup_testnet.sh index bcc73ca..3b98e3f 100755 --- a/local/setup_testnet.sh +++ b/local/setup_testnet.sh @@ -61,11 +61,18 @@ flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strate --network testnet \ --signer testnet-admin +flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ + 'A.7e60df042a9c0868.FlowToken.Vault' \ + "0x4154d5B0E2931a0A1E5b733f19161aa7D2fc4b95" \ + '["0x4154d5B0E2931a0A1E5b733f19161aa7D2fc4b95", "0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e"]' \ + '[3000]' \ + --network testnet \ + --signer testnet-admin flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strategy_composer.cdc \ - 'A.d2580caf2ef07c2f.FlowYieldVaultsStrategies.mUSDCStrategy' \ - 'A.d2580caf2ef07c2f.FlowYieldVaultsStrategies.mUSDCStrategyComposer' \ - /storage/FlowYieldVaultsStrategyComposerIssuer_0xd2580caf2ef07c2f \ + 'A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1.mUSDFStrategy' \ + 'A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1.mUSDFStrategyComposer' \ + /storage/FlowYieldVaultsStrategyV1ComposerIssuer_0xd2580caf2ef07c2f \ --network testnet \ --signer testnet-admin From bd95ca82a88d3d90f990af05f6ac6c6fd7d90f55 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:12:35 -0500 Subject: [PATCH 04/31] fix typo --- cadence/contracts/FlowYieldVaultsStrategiesV1.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc index c8dc369..a3a32a9 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc @@ -383,7 +383,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { access(all) view fun getSupportedComposers(): {Type: Bool} { return { - Type<@mUSDFStrategyComposer>(): true, + Type<@mUSDFStrategyComposer>(): true } } access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} { From 961f187344f183953878a95f66584be2de5e6cce Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:19:29 -0500 Subject: [PATCH 05/31] fix typo --- cadence/contracts/FlowYieldVaultsStrategiesV1.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc index a3a32a9..ab5ceb8 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc @@ -565,7 +565,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { let configs: {Type: {Type: {Type: {String: AnyStruct}}}} = { Type<@mUSDFStrategyComposer>(): { Type<@mUSDFStrategy>(): {} - }, + } } self.account.storage.save(<-create StrategyComposerIssuer(configs: configs), to: self.IssuerStoragePath) From 12b480577edd0891d12fbb1a65dbd9e8b58e2a3b Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:34:37 -0500 Subject: [PATCH 06/31] fix contract --- .../contracts/FlowYieldVaultsStrategiesV1.cdc | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc index ab5ceb8..3f3fa65 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc @@ -215,9 +215,9 @@ access(all) contract FlowYieldVaultsStrategiesV1 { // - MOET -> UNDERLYING (UniV3 Swapper) // - UNDERLYING -> YIELD (ERC4626Swapper) let moetToYieldAMMSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: self.univ3FactoryEVMAddress, - routerAddress: self.univ3RouterEVMAddress, - quoterAddress: self.univ3QuoterEVMAddress, + factoryAddress: FlowYieldVaultsStrategiesV1.univ3FactoryEVMAddress, + routerAddress: FlowYieldVaultsStrategiesV1.univ3RouterEVMAddress, + quoterAddress: FlowYieldVaultsStrategiesV1.univ3QuoterEVMAddress, tokenPath: [moetTokenEVMAddress, yieldTokenEVMAddress], feePath: [100], inVault: moetTokenType, @@ -227,9 +227,9 @@ access(all) contract FlowYieldVaultsStrategiesV1 { ) // Swap MOET -> UNDERLYING via AMM let moetToUnderlyingAssetSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: self.univ3FactoryEVMAddress, - routerAddress: self.univ3RouterEVMAddress, - quoterAddress: self.univ3QuoterEVMAddress, + factoryAddress: FlowYieldVaultsStrategiesV1.univ3FactoryEVMAddress, + routerAddress: FlowYieldVaultsStrategiesV1.univ3RouterEVMAddress, + quoterAddress: FlowYieldVaultsStrategiesV1.univ3QuoterEVMAddress, tokenPath: [moetTokenEVMAddress, underlying4626AssetEVMAddress], feePath: [100], inVault: moetTokenType, @@ -261,9 +261,9 @@ access(all) contract FlowYieldVaultsStrategiesV1 { // YIELD -> MOET // - Targets the MOET <-> YIELD pool as the only route since withdraws from the ERC4626 Vault are async let yieldToMOETSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: self.univ3FactoryEVMAddress, - routerAddress: self.univ3RouterEVMAddress, - quoterAddress: self.univ3QuoterEVMAddress, + factoryAddress: FlowYieldVaultsStrategiesV1.univ3FactoryEVMAddress, + routerAddress: FlowYieldVaultsStrategiesV1.univ3RouterEVMAddress, + quoterAddress: FlowYieldVaultsStrategiesV1.univ3QuoterEVMAddress, tokenPath: [yieldTokenEVMAddress, moetTokenEVMAddress], feePath: [100], inVault: yieldTokenType, @@ -314,9 +314,9 @@ access(all) contract FlowYieldVaultsStrategiesV1 { assert(uniV3FeePath.length > 0, message: "Invalid Uniswap V3 fee path length of \(uniV3FeePath.length)") // initialize the swapper used for recollateralization of the lending position as YIELD increases in value let yieldToFlowSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: self.univ3FactoryEVMAddress, - routerAddress: univ3RouterEVMAddress, - quoterAddress: univ3QuoterEVMAddress, + factoryAddress: FlowYieldVaultsStrategiesV1.univ3FactoryEVMAddress, + routerAddress: FlowYieldVaultsStrategiesV1.univ3RouterEVMAddress, + quoterAddress: FlowYieldVaultsStrategiesV1.univ3QuoterEVMAddress, tokenPath: uniV3AddressPath, feePath: uniV3FeePath, inVault: yieldTokenType, @@ -359,9 +359,6 @@ access(all) contract FlowYieldVaultsStrategiesV1 { } return { - "univ3FactoryEVMAddress": self.univ3FactoryEVMAddress, - "univ3RouterEVMAddress": self.univ3RouterEVMAddress, - "univ3QuoterEVMAddress": self.univ3QuoterEVMAddress, "yieldTokenEVMAddress": yieldTokenEVMAddress, "yieldToCollateralUniV3AddressPaths": { // we’ll store with the collateral vault type as key later From d2ece22ff8ddb2fbac5407207b202139f5b7c1ea Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:10:44 -0500 Subject: [PATCH 07/31] Update cadence/contracts/FlowYieldVaultsStrategies.cdc --- .../contracts/FlowYieldVaultsStrategies.cdc | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategies.cdc b/cadence/contracts/FlowYieldVaultsStrategies.cdc index 650c34d..97542f5 100644 --- a/cadence/contracts/FlowYieldVaultsStrategies.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategies.cdc @@ -533,36 +533,6 @@ access(all) contract FlowYieldVaultsStrategies { access(all) entitlement Configure - access(self) - fun makeCollateralConfig( - yieldTokenEVMAddress: EVM.EVMAddress, - univ3FactoryEVMAddress: EVM.EVMAddress, - univ3RouterEVMAddress: EVM.EVMAddress, - univ3QuoterEVMAddress: EVM.EVMAddress, - yieldToCollateralAddressPath: [EVM.EVMAddress], - yieldToCollateralFeePath: [UInt32] - ): {String: AnyStruct} { - pre { - yieldToCollateralAddressPath.length > 1: - "Invalid Uniswap V3 swap path length" - yieldToCollateralFeePath.length == yieldToCollateralAddressPath.length - 1: - "Uniswap V3 fee path length must be path length - 1" - yieldToCollateralAddressPath[0].equals(yieldTokenEVMAddress): - "UniswapV3 swap path must start with yield token" - } - - return { - "univ3FactoryEVMAddress": univ3FactoryEVMAddress, - "univ3RouterEVMAddress": univ3RouterEVMAddress, - "univ3QuoterEVMAddress": univ3QuoterEVMAddress, - "yieldTokenEVMAddress": yieldTokenEVMAddress, - "yieldToCollateralUniV3AddressPaths": { - // we’ll store with the collateral vault type as key later - } as {Type: [EVM.EVMAddress]}, - "yieldToCollateralUniV3FeePaths": { - } as {Type: [UInt32]} - } - } /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which /// may utilize resource consumption (i.e. account storage). Since TracerStrategy creation consumes account storage /// via configured AutoBalancers From 836530cdbd7ef735bc636b25d32179c9a3cbc5c8 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:34:07 -0500 Subject: [PATCH 08/31] add config --- cadence/contracts/FlowYieldVaultsStrategiesV1.cdc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc index 3f3fa65..ce92e94 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc @@ -1,6 +1,5 @@ // standards import "FungibleToken" -import "FlowToken" import "EVM" // DeFiActions import "DeFiActionsUtils" @@ -21,7 +20,6 @@ import "FlowYieldVaultsAutoBalancers" import "FlowTransactionScheduler" import "FlowYieldVaultsSchedulerRegistry" // tokens -import "YieldToken" import "MOET" // vm bridge import "FlowEVMBridgeConfig" @@ -47,6 +45,8 @@ access(all) contract FlowYieldVaultsStrategiesV1 { access(all) let univ3RouterEVMAddress: EVM.EVMAddress access(all) let univ3QuoterEVMAddress: EVM.EVMAddress + access(all) let config: AnyStruct + /// Canonical StoragePath where the StrategyComposerIssuer should be stored access(all) let IssuerStoragePath: StoragePath @@ -313,7 +313,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { ?? panic("Could not find UniswapV3 fee path for collateral type \(collateralType.identifier)") assert(uniV3FeePath.length > 0, message: "Invalid Uniswap V3 fee path length of \(uniV3FeePath.length)") // initialize the swapper used for recollateralization of the lending position as YIELD increases in value - let yieldToFlowSwapper = UniswapV3SwapConnectors.Swapper( + let yieldToCollateralSwapper = UniswapV3SwapConnectors.Swapper( factoryAddress: FlowYieldVaultsStrategiesV1.univ3FactoryEVMAddress, routerAddress: FlowYieldVaultsStrategiesV1.univ3RouterEVMAddress, quoterAddress: FlowYieldVaultsStrategiesV1.univ3QuoterEVMAddress, @@ -554,6 +554,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress) self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress) self.IssuerStoragePath = StoragePath(identifier: "FlowYieldVaultsStrategyV1ComposerIssuer_\(self.account.address)")! + self.config = {} let moetType = Type<@MOET.Vault>() let moetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>()) From 440221c6582e963bc35749e8cc393ace3a487671 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:39:53 -0500 Subject: [PATCH 09/31] fix typo --- cadence/contracts/FlowYieldVaultsStrategiesV1.cdc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc index ce92e94..3986079 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc @@ -325,7 +325,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { uniqueID: uniqueID ) // allows for YIELD to be deposited to the Position as the collateral basis - let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID) + let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToCollateralSwapper, sink: positionSink, uniqueID: uniqueID) // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value, recollateralizing // the position @@ -554,7 +554,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress) self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress) self.IssuerStoragePath = StoragePath(identifier: "FlowYieldVaultsStrategyV1ComposerIssuer_\(self.account.address)")! - self.config = {} + self.config = {} as AnyStruct let moetType = Type<@MOET.Vault>() let moetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>()) From 6a3c102120f111ffb973c2eecfa28a4495fd9ff2 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:45:57 -0500 Subject: [PATCH 10/31] fix config --- ....cdc => FlowYieldVaultsStrategiesV1_1.cdc} | 173 +++++++++++------- cadence/tests/test_helpers.cdc | 4 +- flow.json | 23 ++- local/setup_emulator.sh | 6 +- local/setup_mainnet.sh | 6 +- local/setup_testnet.sh | 79 +++++++- 6 files changed, 202 insertions(+), 89 deletions(-) rename cadence/contracts/{FlowYieldVaultsStrategiesV1.cdc => FlowYieldVaultsStrategiesV1_1.cdc} (80%) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc similarity index 80% rename from cadence/contracts/FlowYieldVaultsStrategiesV1.cdc rename to cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index 3986079..aac45b3 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -28,7 +28,7 @@ import "FlowEVMBridge" // live oracles import "ERC4626PriceOracles" -/// FlowYieldVaultsStrategiesV1 +/// FlowYieldVaultsStrategiesV1_1 /// /// This contract defines Strategies used in the FlowYieldVaults platform. /// @@ -39,17 +39,42 @@ import "ERC4626PriceOracles" /// A StrategyComposer is tasked with the creation of a supported Strategy. It's within the stacking of DeFiActions /// connectors that the true power of the components lies. /// -access(all) contract FlowYieldVaultsStrategiesV1 { +access(all) contract FlowYieldVaultsStrategiesV1_1 { access(all) let univ3FactoryEVMAddress: EVM.EVMAddress access(all) let univ3RouterEVMAddress: EVM.EVMAddress access(all) let univ3QuoterEVMAddress: EVM.EVMAddress - access(all) let config: AnyStruct + access(all) let config: {String: AnyStruct} /// Canonical StoragePath where the StrategyComposerIssuer should be stored access(all) let IssuerStoragePath: StoragePath + access(all) struct CollateralConfig { + access(all) let yieldTokenEVMAddress: EVM.EVMAddress + access(all) let yieldToCollateralUniV3AddressPath: [EVM.EVMAddress] + access(all) let yieldToCollateralUniV3FeePath: [UInt32] + + init( + yieldTokenEVMAddress: EVM.EVMAddress, + yieldToCollateralUniV3AddressPath: [EVM.EVMAddress], + yieldToCollateralUniV3FeePath: [UInt32] + ) { + pre { + yieldToCollateralUniV3AddressPath.length > 1: + "Invalid UniV3 path length" + yieldToCollateralUniV3FeePath.length == yieldToCollateralUniV3AddressPath.length - 1: + "Invalid UniV3 fee path length" + yieldToCollateralUniV3AddressPath[0].equals(yieldTokenEVMAddress): + "UniV3 path must start with yield token" + } + + self.yieldTokenEVMAddress = yieldTokenEVMAddress + self.yieldToCollateralUniV3AddressPath = yieldToCollateralUniV3AddressPath + self.yieldToCollateralUniV3FeePath = yieldToCollateralUniV3FeePath + } + } + /// This strategy uses mUSDF vaults access(all) resource mUSDFStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource { /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol- @@ -112,10 +137,10 @@ access(all) contract FlowYieldVaultsStrategiesV1 { /// This StrategyComposer builds a mUSDFStrategy access(all) resource mUSDFStrategyComposer : FlowYieldVaults.StrategyComposer { - /// { Strategy Type: { Collateral Type: { String: AnyStruct } } } - access(self) let config: {Type: {Type: {String: AnyStruct}}} + /// { Strategy Type: { Collateral Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig } } + access(self) let config: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}} - init(_ config: {Type: {Type: {String: AnyStruct}}}) { + init(_ config: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}) { self.config = config } @@ -131,7 +156,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { /// Returns the Vault types which can be used to initialize a given Strategy access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} { let supported: {Type: Bool} = {} - if let strategyConfig = &self.config[forStrategy] as &{Type: {String: AnyStruct}}? { + if let strategyConfig = &self.config[forStrategy] as &{Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}? { for collateralType in strategyConfig.keys { supported[collateralType] = true } @@ -165,7 +190,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { let moetTokenType: Type = Type<@MOET.Vault>() let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType) ?? panic("Token Vault type \(moetTokenType.identifier) has not yet been registered with the VMbridge") - let yieldTokenEVMAddress = collateralConfig["yieldTokenEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"yieldTokenEVMAddress\" in config") + let yieldTokenEVMAddress = collateralConfig.yieldTokenEVMAddress let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress) ?? panic("Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())") @@ -185,7 +210,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { ) // Create recurring config for automatic rebalancing - let recurringConfig = FlowYieldVaultsStrategiesV1._createRecurringConfig(withID: uniqueID) + let recurringConfig = FlowYieldVaultsStrategiesV1_1._createRecurringConfig(withID: uniqueID) // configure and AutoBalancer for this stack with native recurring scheduling let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer( @@ -215,34 +240,34 @@ access(all) contract FlowYieldVaultsStrategiesV1 { // - MOET -> UNDERLYING (UniV3 Swapper) // - UNDERLYING -> YIELD (ERC4626Swapper) let moetToYieldAMMSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: FlowYieldVaultsStrategiesV1.univ3FactoryEVMAddress, - routerAddress: FlowYieldVaultsStrategiesV1.univ3RouterEVMAddress, - quoterAddress: FlowYieldVaultsStrategiesV1.univ3QuoterEVMAddress, + factoryAddress: FlowYieldVaultsStrategiesV1_1.univ3FactoryEVMAddress, + routerAddress: FlowYieldVaultsStrategiesV1_1.univ3RouterEVMAddress, + quoterAddress: FlowYieldVaultsStrategiesV1_1.univ3QuoterEVMAddress, tokenPath: [moetTokenEVMAddress, yieldTokenEVMAddress], feePath: [100], inVault: moetTokenType, outVault: yieldTokenType, - coaCapability: FlowYieldVaultsStrategiesV1._getCOACapability(), + coaCapability: FlowYieldVaultsStrategiesV1_1._getCOACapability(), uniqueID: uniqueID ) // Swap MOET -> UNDERLYING via AMM let moetToUnderlyingAssetSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: FlowYieldVaultsStrategiesV1.univ3FactoryEVMAddress, - routerAddress: FlowYieldVaultsStrategiesV1.univ3RouterEVMAddress, - quoterAddress: FlowYieldVaultsStrategiesV1.univ3QuoterEVMAddress, + factoryAddress: FlowYieldVaultsStrategiesV1_1.univ3FactoryEVMAddress, + routerAddress: FlowYieldVaultsStrategiesV1_1.univ3RouterEVMAddress, + quoterAddress: FlowYieldVaultsStrategiesV1_1.univ3QuoterEVMAddress, tokenPath: [moetTokenEVMAddress, underlying4626AssetEVMAddress], feePath: [100], inVault: moetTokenType, outVault: underlying4626AssetType, - coaCapability: FlowYieldVaultsStrategiesV1._getCOACapability(), + coaCapability: FlowYieldVaultsStrategiesV1_1._getCOACapability(), uniqueID: uniqueID ) // Swap UNDERLYING -> YIELD via ERC4626 Vault let underlyingTo4626Swapper = ERC4626SwapConnectors.Swapper( asset: underlying4626AssetType, vault: yieldTokenEVMAddress, - coa: FlowYieldVaultsStrategiesV1._getCOACapability(), - feeSource: FlowYieldVaultsStrategiesV1._createFeeSource(withID: uniqueID), + coa: FlowYieldVaultsStrategiesV1_1._getCOACapability(), + feeSource: FlowYieldVaultsStrategiesV1_1._createFeeSource(withID: uniqueID), uniqueID: uniqueID ) // Compose v3 swapper & 4626 swapper into sequential swapper for MOET -> UNDERLYING -> YIELD @@ -261,14 +286,14 @@ access(all) contract FlowYieldVaultsStrategiesV1 { // YIELD -> MOET // - Targets the MOET <-> YIELD pool as the only route since withdraws from the ERC4626 Vault are async let yieldToMOETSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: FlowYieldVaultsStrategiesV1.univ3FactoryEVMAddress, - routerAddress: FlowYieldVaultsStrategiesV1.univ3RouterEVMAddress, - quoterAddress: FlowYieldVaultsStrategiesV1.univ3QuoterEVMAddress, + factoryAddress: FlowYieldVaultsStrategiesV1_1.univ3FactoryEVMAddress, + routerAddress: FlowYieldVaultsStrategiesV1_1.univ3RouterEVMAddress, + quoterAddress: FlowYieldVaultsStrategiesV1_1.univ3QuoterEVMAddress, tokenPath: [yieldTokenEVMAddress, moetTokenEVMAddress], feePath: [100], inVault: yieldTokenType, outVault: moetTokenType, - coaCapability: FlowYieldVaultsStrategiesV1._getCOACapability(), + coaCapability: FlowYieldVaultsStrategiesV1_1._getCOACapability(), uniqueID: uniqueID ) @@ -280,7 +305,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToMOETSwapper, source: abaSource, uniqueID: uniqueID) // open a FlowCreditMarket position - let poolCap = FlowYieldVaultsStrategiesV1.account.storage.copy>( + let poolCap = FlowYieldVaultsStrategiesV1_1.account.storage.copy>( from: FlowCreditMarket.PoolCapStoragePath ) ?? panic("Missing or invalid pool capability") let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap") @@ -300,28 +325,24 @@ access(all) contract FlowYieldVaultsStrategiesV1 { // init YieldToken -> Collateral Swapper // // get UniswapV3 path configs - let collateralUniV3AddressPathConfig = collateralConfig["yieldToCollateralUniV3AddressPaths"] as? {Type: [EVM.EVMAddress]} - ?? panic("Could not find UniswapV3 address paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)") - let uniV3AddressPath = collateralUniV3AddressPathConfig[collateralType] - ?? panic("Could not find UniswapV3 address path for collateral type \(collateralType.identifier)") + var uniV3AddressPath: [EVM.EVMAddress] = collateralConfig.yieldToCollateralUniV3AddressPath assert(uniV3AddressPath.length > 1, message: "Invalid Uniswap V3 swap path length of \(uniV3AddressPath.length)") assert(uniV3AddressPath[0].equals(yieldTokenEVMAddress), message: "UniswapV3 swap path does not match - expected path[0] to be \(yieldTokenEVMAddress.toString()) but found \(uniV3AddressPath[0].toString())") - let collateralUniV3FeePathConfig = collateralConfig["yieldToCollateralUniV3FeePaths"] as? {Type: [UInt32]} - ?? panic("Could not find UniswapV3 fee paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)") - let uniV3FeePath = collateralUniV3FeePathConfig[collateralType] - ?? panic("Could not find UniswapV3 fee path for collateral type \(collateralType.identifier)") + + var uniV3FeePath = collateralConfig.yieldToCollateralUniV3FeePath assert(uniV3FeePath.length > 0, message: "Invalid Uniswap V3 fee path length of \(uniV3FeePath.length)") + // initialize the swapper used for recollateralization of the lending position as YIELD increases in value let yieldToCollateralSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: FlowYieldVaultsStrategiesV1.univ3FactoryEVMAddress, - routerAddress: FlowYieldVaultsStrategiesV1.univ3RouterEVMAddress, - quoterAddress: FlowYieldVaultsStrategiesV1.univ3QuoterEVMAddress, + factoryAddress: FlowYieldVaultsStrategiesV1_1.univ3FactoryEVMAddress, + routerAddress: FlowYieldVaultsStrategiesV1_1.univ3RouterEVMAddress, + quoterAddress: FlowYieldVaultsStrategiesV1_1.univ3QuoterEVMAddress, tokenPath: uniV3AddressPath, feePath: uniV3FeePath, inVault: yieldTokenType, outVault: collateralType, - coaCapability: FlowYieldVaultsStrategiesV1._getCOACapability(), + coaCapability: FlowYieldVaultsStrategiesV1_1._getCOACapability(), uniqueID: uniqueID ) // allows for YIELD to be deposited to the Position as the collateral basis @@ -341,6 +362,12 @@ access(all) contract FlowYieldVaultsStrategiesV1 { } } + access(all) fun createIssuer( + configs: {Type: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}} + ): @StrategyComposerIssuer { + return <- create StrategyComposerIssuer(configs: configs) + } + access(all) entitlement Configure access(self) @@ -348,7 +375,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { yieldTokenEVMAddress: EVM.EVMAddress, yieldToCollateralAddressPath: [EVM.EVMAddress], yieldToCollateralFeePath: [UInt32] - ): {String: AnyStruct} { + ): CollateralConfig { pre { yieldToCollateralAddressPath.length > 1: "Invalid Uniswap V3 swap path length" @@ -358,26 +385,39 @@ access(all) contract FlowYieldVaultsStrategiesV1 { "UniswapV3 swap path must start with yield token" } - return { - "yieldTokenEVMAddress": yieldTokenEVMAddress, - "yieldToCollateralUniV3AddressPaths": { - // we’ll store with the collateral vault type as key later - } as {Type: [EVM.EVMAddress]}, - "yieldToCollateralUniV3FeePaths": { - } as {Type: [UInt32]} - } + return CollateralConfig( + yieldTokenEVMAddress: yieldTokenEVMAddress, + yieldToCollateralUniV3AddressPath: yieldToCollateralAddressPath, + yieldToCollateralUniV3FeePath: yieldToCollateralFeePath + ) } /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which /// may utilize resource consumption (i.e. account storage). Since Strategy creation consumes account storage /// via configured AutoBalancers access(all) resource StrategyComposerIssuer : FlowYieldVaults.StrategyComposerIssuer { - /// { StrategyComposer Type: { Strategy Type: { Collateral Type: { String: AnyStruct } } } } - access(all) let configs: {Type: {Type: {Type: {String: AnyStruct}}}} + /// { StrategyComposer Type: { Strategy Type: { Collateral Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig } } } + access(all) var configs: {Type: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}} - init(configs: {Type: {Type: {Type: {String: AnyStruct}}}}) { + init(configs: {Type: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}}) { self.configs = configs } + access(all) view fun hasConfig( + composer: Type, + strategy: Type, + collateral: Type + ): Bool { + let composerConfig = self.configs[composer]! + if composerConfig == nil { + return false + } + let strategyConfig = composerConfig[strategy]! + if strategyConfig == nil { + return false + } + return strategyConfig![collateral] != nil + } + access(all) view fun getSupportedComposers(): {Type: Bool} { return { Type<@mUSDFStrategyComposer>(): true @@ -387,7 +427,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { pre { self.getSupportedComposers()[type] == true: "Unsupported StrategyComposer \(type.identifier) requested" - (&self.configs[type] as &{Type: {Type: {String: AnyStruct}}}?) != nil: + (&self.configs[type] as &{Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}?) != nil: "Could not find config for StrategyComposer \(type.identifier)" } switch type { @@ -401,7 +441,7 @@ access(all) contract FlowYieldVaultsStrategiesV1 { access(Configure) fun upsertConfigFor( composer: Type, - config: {Type: {Type: {String: AnyStruct}}} + config: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}} ) { pre { self.getSupportedComposers()[composer] == true: @@ -420,12 +460,12 @@ access(all) contract FlowYieldVaultsStrategiesV1 { // Merge instead of overwrite let existingComposerConfig = self.configs[composer] ?? {} - var mergedComposerConfig: {Type: {Type: {String: AnyStruct}}} = existingComposerConfig + var mergedComposerConfig: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}} = existingComposerConfig for stratType in config.keys { let newPerCollateral = config[stratType]! let existingPerCollateral = mergedComposerConfig[stratType] ?? {} - var mergedPerCollateral: {Type: {String: AnyStruct}} = existingPerCollateral + var mergedPerCollateral: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig} = existingPerCollateral for collateralType in newPerCollateral.keys { mergedPerCollateral[collateralType] = newPerCollateral[collateralType]! @@ -454,26 +494,14 @@ access(all) contract FlowYieldVaultsStrategiesV1 { } // Base struct with shared addresses - var base = FlowYieldVaultsStrategiesV1.makeCollateralConfig( + var base = FlowYieldVaultsStrategiesV1_1.makeCollateralConfig( yieldTokenEVMAddress: yieldTokenEVMAddress, yieldToCollateralAddressPath: yieldToCollateralAddressPath, yieldToCollateralFeePath: yieldToCollateralFeePath ) - // Plug in the type-keyed maps - let addressPaths = { - collateralVaultType: yieldToCollateralAddressPath - } as {Type: [EVM.EVMAddress]} - - let feePaths = { - collateralVaultType: yieldToCollateralFeePath - } as {Type: [UInt32]} - - base["yieldToCollateralUniV3AddressPaths"] = addressPaths - base["yieldToCollateralUniV3FeePaths"] = feePaths - // Wrap into the nested config expected by upsertConfigFor - let singleCollateralConfig: {Type: {Type: {String: AnyStruct}}} = { + let singleCollateralConfig: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}} = { strategyType: { collateralVaultType: base } @@ -481,6 +509,13 @@ access(all) contract FlowYieldVaultsStrategiesV1 { self.upsertConfigFor(composer: composer, config: singleCollateralConfig) } + access(Configure) fun purgeConfig() { + self.configs = { + Type<@mUSDFStrategyComposer>(): { + Type<@mUSDFStrategy>(): {} + } + } as {Type: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}} + } } /// Returns the COA capability for this account @@ -553,14 +588,14 @@ access(all) contract FlowYieldVaultsStrategiesV1 { self.univ3FactoryEVMAddress = EVM.addressFromString(univ3FactoryEVMAddress) self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress) self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress) - self.IssuerStoragePath = StoragePath(identifier: "FlowYieldVaultsStrategyV1ComposerIssuer_\(self.account.address)")! - self.config = {} as AnyStruct + self.IssuerStoragePath = StoragePath(identifier: "FlowYieldVaultsStrategyV1_1ComposerIssuer_\(self.account.address)")! + self.config = {} let moetType = Type<@MOET.Vault>() let moetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>()) ?? panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge") - let configs: {Type: {Type: {Type: {String: AnyStruct}}}} = { + let configs: {Type: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}} = { Type<@mUSDFStrategyComposer>(): { Type<@mUSDFStrategy>(): {} } diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 6ff83aa..8c0c92e 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -330,8 +330,8 @@ access(all) fun deployContracts() { Test.expect(err, Test.beNil()) err = Test.deployContract( - name: "FlowYieldVaultsStrategiesV1", - path: "../contracts/FlowYieldVaultsStrategiesV1.cdc", + name: "FlowYieldVaultsStrategiesV1_1", + path: "../contracts/FlowYieldVaultsStrategiesV1_1.cdc", arguments: [ "0x986Cb42b0557159431d48fE0A40073296414d410", "0x92657b195e22b69E4779BBD09Fa3CD46F0CF8e39", diff --git a/flow.json b/flow.json index a18476d..db5252a 100644 --- a/flow.json +++ b/flow.json @@ -161,8 +161,8 @@ "testnet": "d2580caf2ef07c2f" } }, - "FlowYieldVaultsStrategiesV1": { - "source": "cadence/contracts/FlowYieldVaultsStrategiesV1.cdc", + "FlowYieldVaultsStrategiesV1_1": { + "source": "cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc", "aliases": { "emulator": "045a1763c93006ca", "mainnet": "b1d63873c3cc9f79", @@ -701,6 +701,17 @@ "testnet": "access.devnet.nodes.onflow.org:9000" }, "accounts": { + "alex": { + "address": "106d71dfc4beabb0", + "key": { + "type": "hex", + "index": 0, + "signatureAlgorithm": "ECDSA_secP256", + "signatureAlgorithm": "ECDSA_secp256k1", + "hashAlgorithm": "SHA2_256", + "privateKey": "fe32cb43d3d214c2122202b94d2be0affa8f4ab995181f72ab8fd2fa5f4fde95" + } + }, "emulator-account": { "address": "f8d6e0586b0a20c7", "key": { @@ -858,7 +869,7 @@ ] }, { - "name": "FlowYieldVaultsStrategiesV1", + "name": "FlowYieldVaultsStrategiesV1_1", "args": [ { "value": "0x986Cb42b0557159431d48fE0A40073296414d410", @@ -969,7 +980,7 @@ ] }, { - "name": "FlowYieldVaultsStrategiesV1", + "name": "FlowYieldVaultsStrategiesV1_1", "args": [ { "value": "0xca6d7Bb03334bBf135902e1d919a5feccb461632", @@ -1057,7 +1068,7 @@ ] }, { - "name": "FlowYieldVaultsStrategiesV1", + "name": "FlowYieldVaultsStrategiesV1_1", "args": [ { "value": "0x92657b195e22b69E4779BBD09Fa3CD46F0CF8e39", @@ -1076,4 +1087,4 @@ ] } } -} \ No newline at end of file +} diff --git a/local/setup_emulator.sh b/local/setup_emulator.sh index eb552e6..4a44d2a 100755 --- a/local/setup_emulator.sh +++ b/local/setup_emulator.sh @@ -51,9 +51,9 @@ flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strate flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strategy_composer.cdc \ - 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1.USDFStrategy' \ - 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1.USDFStrategyComposer' \ - /storage/FlowYieldVaultsStrategyV1ComposerIssuer_0x045a1763c93006ca \ + 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1_1.USDFStrategy' \ + 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1_1.USDFStrategyComposer' \ + /storage/FlowYieldVaultsStrategyV1_1ComposerIssuer_0x045a1763c93006ca \ --signer emulator-flow-yield-vaults # grant PoolBeta cap diff --git a/local/setup_mainnet.sh b/local/setup_mainnet.sh index da58c35..ffdcf54 100755 --- a/local/setup_mainnet.sh +++ b/local/setup_mainnet.sh @@ -69,9 +69,9 @@ flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_mu # # add TracerStrategy as supported Strategy with the ability to initialize when new YieldVaults are created flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strategy_composer.cdc \ - 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1.mUSDFStrategy' \ - 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1.mUSDFStrategyComposer' \ - /storage/FlowYieldVaultsStrategyV1ComposerIssuer_0xb1d63873c3cc9f79 \ + 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy' \ + 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1_1.mUSDFStrategyComposer' \ + /storage/FlowYieldVaultsStrategyV1_1ComposerIssuer_0xb1d63873c3cc9f79 \ --network mainnet \ --signer mainnet-admin diff --git a/local/setup_testnet.sh b/local/setup_testnet.sh index 3b98e3f..a402b66 100755 --- a/local/setup_testnet.sh +++ b/local/setup_testnet.sh @@ -15,8 +15,8 @@ flow transactions send ./lib/flow-evm-bridge/cadence/transactions/bridge/onboard # configure FlowCreditMarket # # add MOET - USD association on Band Oracle -flow transactions send ../lib/FlowCreditMarket/FlowActions/cadence/transactions/band-oracle-connector/add_symbol.cdc "USD" "A.426f0458ced60037.MOET.Vault" -# +cd ./libs/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "USD" "A.426f0458ced60037.MOET.Vault" --network testnet --signer testnet-band-oracle-connectors + # create Pool with MOET as default token with Mock Oracle flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-factory/create_and_store_pool.cdc 'A.426f0458ced60037.MOET.Vault' --network testnet --signer testnet-flow-credit-market-deployer # update Pool with Band Oracle instead of Mock Oracle @@ -31,6 +31,32 @@ flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-m --network testnet \ --signer testnet-flow-credit-market-deployer +# add WBTC to band oracle +cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "BTC" "A.dfc20aee650fcbdf.EVMVMBridgedToken_208d09d2a6dd176e3e95b3f0de172a7471c5b2d6.Vault" --network testnet --signer testnet-band-oracle-connectors && cd ../../.. + +# add WETH to band oracle +cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "ETH" "A.dfc20aee650fcbdf.EVMVMBridgedToken_059a77239dafa770977dd9f1e98632c3e4559848.Vault" --network testnet --signer testnet-band-oracle-connectors && cd ../../.. + +# add WBTC as supported token +flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_simple_interest_curve.cdc \ + 'A.dfc20aee650fcbdf.EVMVMBridgedToken_208d09d2a6dd176e3e95b3f0de172a7471c5b2d6.Vault' \ + 0.8 \ + 1.0 \ + 1_000_000.0 \ + 1_000_000.0 \ + --network testnet \ + --signer testnet-flow-credit-market-deployer + +# add WETH as supported token +flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_simple_interest_curve.cdc \ + 'A.dfc20aee650fcbdf.EVMVMBridgedToken_059a77239dafa770977dd9f1e98632c3e4559848.Vault' \ + 0.8 \ + 1.0 \ + 1_000_000.0 \ + 1_000_000.0 \ + --network testnet \ + --signer testnet-flow-credit-market-deployer + echo "swap Flow to MOET" flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/create_position.cdc 100000.0 --network testnet --signer testnet-flow-credit-market-deployer @@ -50,6 +76,7 @@ flow transactions send ./cadence/transactions/mocks/swapper/set_liquidity_connec flow transactions send ./cadence/transactions/mocks/swapper/set_liquidity_connector.cdc /storage/moetTokenVault_0x426f0458ced60037 --network testnet --signer testnet-admin #flow transactions send ./cadence/transactions/mocks/swapper/set_liquidity_connector.cdc /storage/yieldTokenVault_0xd2580caf2ef07c2f --network testnet --signer testnet-admin +# setup yield token vault flow transactions send ./lib/FlowCreditMarket/FlowActions/cadence/transactions/fungible-tokens/setup_generic_vault.cdc 'A.dfc20aee650fcbdf.EVMVMBridgedToken_4154d5b0e2931a0a1e5b733f19161aa7d2fc4b95.Vault' --network testnet --signer testnet-admin flow transactions send ./cadence/transactions/mocks/swapper/set_liquidity_connector.cdc /storage/EVMVMBridgedToken_4154d5b0e2931a0a1e5b733f19161aa7d2fc4b95Vault --network testnet --signer testnet-admin @@ -61,7 +88,7 @@ flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strate --network testnet \ --signer testnet-admin -flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ +flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ 'A.7e60df042a9c0868.FlowToken.Vault' \ "0x4154d5B0E2931a0A1E5b733f19161aa7D2fc4b95" \ '["0x4154d5B0E2931a0A1E5b733f19161aa7D2fc4b95", "0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e"]' \ @@ -69,10 +96,28 @@ flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_mu --network testnet \ --signer testnet-admin +# WETH univ3 path and fees +flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ + 'A.dfc20aee650fcbdf.EVMVMBridgedToken_059a77239dafa770977dd9f1e98632c3e4559848.Vault' \ + "0x4154d5B0E2931a0A1E5b733f19161aa7D2fc4b95" \ + '["0x4154d5B0E2931a0A1E5b733f19161aa7D2fc4b95","0x02d3575e2516a515E9B91a52b294Edc80DC7987c", "0x059A77239daFa770977DD9f1E98632C3E4559848"]' \ + '[3000,3000]' \ + --network testnet \ + --signer testnet-admin + +# WBTC univ3 path and fees +flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ + 'A.dfc20aee650fcbdf.EVMVMBridgedToken_208d09d2a6dd176e3e95b3f0de172a7471c5b2d6.Vault' \ + "0x4154d5B0E2931a0A1E5b733f19161aa7D2fc4b95" \ + '["0x4154d5B0E2931a0A1E5b733f19161aa7D2fc4b95","0x02d3575e2516a515E9B91a52b294Edc80DC7987c","0x208d09d2a6Dd176e3e95b3F0DE172A7471C5B2d6"]' \ + '[3000,3000]' \ + --network testnet \ + --signer testnet-admin + flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strategy_composer.cdc \ - 'A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1.mUSDFStrategy' \ - 'A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1.mUSDFStrategyComposer' \ - /storage/FlowYieldVaultsStrategyV1ComposerIssuer_0xd2580caf2ef07c2f \ + 'A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy' \ + 'A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1_1.mUSDFStrategyComposer' \ + /storage/FlowYieldVaultsStrategyV1_1ComposerIssuer_0xd2580caf2ef07c2f \ --network testnet \ --signer testnet-admin @@ -94,6 +139,9 @@ flow transactions send ./lib/flow-evm-bridge/cadence/transactions/flow-token/tra # --proposer \ # --payer testnet-admin \ # --network testnet +# +# +# Flow # flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ # A.d2580caf2ef07c2f.FlowYieldVaultsStrategies.mUSDCStrategy \ # A.7e60df042a9c0868.FlowToken.Vault \ @@ -101,3 +149,22 @@ flow transactions send ./lib/flow-evm-bridge/cadence/transactions/flow-token/tra # --signer \ # --compute-limit 9999 \ # --network testnet +# +# +# WBTC (BTCf) +flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ + A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy \ + A.dfc20aee650fcbdf.EVMVMBridgedToken_208d09d2a6dd176e3e95b3f0de172a7471c5b2d6.Vault \ + 0.00001 \ + --signer alex \ + --compute-limit 9999 \ + --network testnet +# +# WETH (ETHf) +# flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ +# A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy \ +# A.dfc20aee650fcbdf.EVMVMBridgedToken_059a77239dafa770977dd9f1e98632c3e4559848.Vault \ +# 0.001 \ +# --signer \ +# --compute-limit 9999 \ +# --network testnet From 0dc3ae94ab5da7082bbb92edf4e5918dee1694b7 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 22 Dec 2025 10:18:56 -0500 Subject: [PATCH 11/31] Update flow.json --- flow.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/flow.json b/flow.json index 135e5b1..8854daf 100644 --- a/flow.json +++ b/flow.json @@ -708,17 +708,6 @@ "testnet": "access.devnet.nodes.onflow.org:9000" }, "accounts": { - "alex": { - "address": "106d71dfc4beabb0", - "key": { - "type": "hex", - "index": 0, - "signatureAlgorithm": "ECDSA_secP256", - "signatureAlgorithm": "ECDSA_secp256k1", - "hashAlgorithm": "SHA2_256", - "privateKey": "fe32cb43d3d214c2122202b94d2be0affa8f4ab995181f72ab8fd2fa5f4fde95" - } - }, "emulator-account": { "address": "f8d6e0586b0a20c7", "key": { From d9ee84611885305badae77d7b886090bb544152f Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 22 Dec 2025 13:33:59 -0500 Subject: [PATCH 12/31] add mainnet setup --- local/setup_mainnet.sh | 21 +++++++++++++++++++++ local/setup_testnet.sh | 16 ++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/local/setup_mainnet.sh b/local/setup_mainnet.sh index c368613..fb8eb26 100755 --- a/local/setup_mainnet.sh +++ b/local/setup_mainnet.sh @@ -58,6 +58,8 @@ flow transactions send ./lib/FlowCreditMarket/FlowActions/cadence/transactions/f # flow transactions send ./cadence/transactions/mocks/swapper/set_liquidity_connector.cdc /storage/EVMVMBridgedToken_4154d5b0e2931a0a1e5b733f19161aa7d2fc4b95Vault --network mainnet --signer mainnet-admin # + +# Setup UniV3 path tauUSDFv -> USDF -> WFLOW flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ 'A.1654653399040a61.FlowToken.Vault' \ "0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c" \ @@ -66,6 +68,25 @@ flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_mu --network mainnet \ --signer mainnet-admin + +# Setup UniV3 path tauUSDFv -> USDF -> WBTC +flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ + 'A.0x1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault' \ + "0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c" \ + '["0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c","0x2aaBea2058b5aC2D339b163C6Ab6f2b6d53aabED","0x717DAE2BaF7656BE9a9B01deE31d571a9d4c9579"]' \ + '[100,3000]' \ + --network mainnet \ + --signer mainnet-admin + +# Setup UniV3 path tauUSDFv -> USDF -> WETH +flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ + 'A.0x1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault' \ + "0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c" \ + '["0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c","0x2aaBea2058b5aC2D339b163C6Ab6f2b6d53aabED","0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590"]' \ + '[100,3000]' \ + --network mainnet \ + --signer mainnet-admin + # # add TracerStrategy as supported Strategy with the ability to initialize when new YieldVaults are created flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strategy_composer.cdc \ diff --git a/local/setup_testnet.sh b/local/setup_testnet.sh index a402b66..4273234 100755 --- a/local/setup_testnet.sh +++ b/local/setup_testnet.sh @@ -143,7 +143,7 @@ flow transactions send ./lib/flow-evm-bridge/cadence/transactions/flow-token/tra # # Flow # flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ -# A.d2580caf2ef07c2f.FlowYieldVaultsStrategies.mUSDCStrategy \ +# A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy \ # A.7e60df042a9c0868.FlowToken.Vault \ # 100.0 \ # --signer \ @@ -152,13 +152,13 @@ flow transactions send ./lib/flow-evm-bridge/cadence/transactions/flow-token/tra # # # WBTC (BTCf) -flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ - A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy \ - A.dfc20aee650fcbdf.EVMVMBridgedToken_208d09d2a6dd176e3e95b3f0de172a7471c5b2d6.Vault \ - 0.00001 \ - --signer alex \ - --compute-limit 9999 \ - --network testnet +# flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ +# A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy \ +# A.dfc20aee650fcbdf.EVMVMBridgedToken_208d09d2a6dd176e3e95b3f0de172a7471c5b2d6.Vault \ +# 0.00001 \ +# --signer \ +# --compute-limit 9999 \ +# --network testnet # # WETH (ETHf) # flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ From 7d6c2bb45b1aefac26fd21f9ff826c0a0bda4064 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 22 Dec 2025 13:35:04 -0500 Subject: [PATCH 13/31] temporarily delete strategy --- .../contracts/FlowYieldVaultsStrategies.cdc | 705 ------------------ 1 file changed, 705 deletions(-) delete mode 100644 cadence/contracts/FlowYieldVaultsStrategies.cdc diff --git a/cadence/contracts/FlowYieldVaultsStrategies.cdc b/cadence/contracts/FlowYieldVaultsStrategies.cdc deleted file mode 100644 index f585956..0000000 --- a/cadence/contracts/FlowYieldVaultsStrategies.cdc +++ /dev/null @@ -1,705 +0,0 @@ -// standards -import "FungibleToken" -import "FlowToken" -import "EVM" -// DeFiActions -import "DeFiActionsUtils" -import "DeFiActions" -import "SwapConnectors" -import "FungibleTokenConnectors" -// amm integration -import "UniswapV3SwapConnectors" -import "ERC4626SwapConnectors" -import "ERC4626Utils" -// Lending protocol -import "FlowCreditMarket" -// FlowYieldVaults platform -import "FlowYieldVaultsClosedBeta" -import "FlowYieldVaults" -import "FlowYieldVaultsAutoBalancers" -// scheduler -import "FlowTransactionScheduler" -import "FlowYieldVaultsSchedulerRegistry" -// tokens -import "YieldToken" -import "MOET" -// vm bridge -import "FlowEVMBridgeConfig" -import "FlowEVMBridgeUtils" -import "FlowEVMBridge" -// live oracles -import "ERC4626PriceOracles" -// mocks -import "MockOracle" -import "MockSwapper" - -/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION -/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -/// -/// FlowYieldVaultsStrategies -/// -/// This contract defines Strategies used in the FlowYieldVaults platform. -/// -/// A Strategy instance can be thought of as objects wrapping a stack of DeFiActions connectors wired together to -/// (optimally) generate some yield on initial deposits. Strategies can be simple such as swapping into a yield-bearing -/// asset (such as stFLOW) or more complex DeFiActions stacks. -/// -/// A StrategyComposer is tasked with the creation of a supported Strategy. It's within the stacking of DeFiActions -/// connectors that the true power of the components lies. -/// -access(all) contract FlowYieldVaultsStrategies { - - access(all) let univ3FactoryEVMAddress: EVM.EVMAddress - access(all) let univ3RouterEVMAddress: EVM.EVMAddress - access(all) let univ3QuoterEVMAddress: EVM.EVMAddress - access(all) let yieldTokenEVMAddress: EVM.EVMAddress - - /// Canonical StoragePath where the StrategyComposerIssuer should be stored - access(all) let IssuerStoragePath: StoragePath - - /// This is the first Strategy implementation, wrapping a FlowCreditMarket Position along with its related Sink & - /// Source. While this object is a simple wrapper for the top-level collateralized position, the true magic of the - /// DeFiActions is in the stacking of the related connectors. This stacking logic can be found in the - /// TracerStrategyComposer construct. - access(all) resource TracerStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource { - /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol- - /// specific Identifier to associated connectors on construction - access(contract) var uniqueID: DeFiActions.UniqueIdentifier? - access(self) let position: FlowCreditMarket.Position - access(self) var sink: {DeFiActions.Sink} - access(self) var source: {DeFiActions.Source} - - init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowCreditMarket.Position) { - self.uniqueID = id - self.position = position - self.sink = position.createSink(type: collateralType) - self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) - } - - // Inherited from FlowYieldVaults.Strategy default implementation - // access(all) view fun isSupportedCollateralType(_ type: Type): Bool - - access(all) view fun getSupportedCollateralTypes(): {Type: Bool} { - return { self.sink.getSinkType(): true } - } - /// Returns the amount available for withdrawal via the inner Source - access(all) fun availableBalance(ofToken: Type): UFix64 { - return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0 - } - /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference - access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) { - self.sink.depositCapacity(from: from) - } - /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported, - /// an empty Vault is returned. - access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} { - if ofToken != self.source.getSourceType() { - return <- DeFiActionsUtils.getEmptyVault(ofToken) - } - return <- self.source.withdrawAvailable(maxAmount: maxAmount) - } - /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer - access(contract) fun burnCallback() { - FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!) - } - access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { - return DeFiActions.ComponentInfo( - type: self.getType(), - id: self.id(), - innerComponents: [] - ) - } - access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? { - return self.uniqueID - } - access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) { - self.uniqueID = id - } - } - - /// This StrategyComposer builds a TracerStrategy - access(all) resource TracerStrategyComposer : FlowYieldVaults.StrategyComposer { - /// Returns the Types of Strategies composed by this StrategyComposer - access(all) view fun getComposedStrategyTypes(): {Type: Bool} { - return { Type<@TracerStrategy>(): true } - } - - /// Returns the Vault types which can be used to initialize a given Strategy - access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} { - return { Type<@FlowToken.Vault>(): true } - } - - /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the - /// provided Vault type - access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} { - return { Type<@FlowToken.Vault>(): true } - } - - /// Composes a Strategy of the given type with the provided funds - access(all) fun createStrategy( - _ type: Type, - uniqueID: DeFiActions.UniqueIdentifier, - withFunds: @{FungibleToken.Vault} - ): @{FlowYieldVaults.Strategy} { - // this PriceOracle is mocked and will be shared by all components used in the TracerStrategy - // TODO: add ERC4626 price oracle - let oracle = MockOracle.PriceOracle() - - // assign token types - - let moetTokenType: Type = Type<@MOET.Vault>() - let yieldTokenType = Type<@YieldToken.Vault>() - // assign collateral & flow token types - let collateralType = withFunds.getType() - - // Create recurring config for automatic rebalancing - let recurringConfig = FlowYieldVaultsStrategies._createRecurringConfig(withID: uniqueID) - - // configure and AutoBalancer for this stack with native recurring scheduling - let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer( - oracle: oracle, // used to determine value of deposits & when to rebalance - vaultType: yieldTokenType, // the type of Vault held by the AutoBalancer - lowerThreshold: 0.95, // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits - upperThreshold: 1.05, // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits - rebalanceSink: nil, // nil on init - will be set once a PositionSink is available - rebalanceSource: nil, // nil on init - not set for TracerStrategy - recurringConfig: recurringConfig, // enables native AutoBalancer self-scheduling - uniqueID: uniqueID // identifies AutoBalancer as part of this Strategy - ) - // enables deposits of YieldToken to the AutoBalancer - let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") - // enables withdrawals of YieldToken from the AutoBalancer - let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") - - // init Stable <> YIELD swappers - // - // Stable -> YieldToken - let stableToYieldSwapper = MockSwapper.Swapper( - inVault: moetTokenType, - outVault: yieldTokenType, - uniqueID: uniqueID - ) - // YieldToken -> Stable - let yieldToStableSwapper = MockSwapper.Swapper( - inVault: yieldTokenType, - outVault: moetTokenType, - uniqueID: uniqueID - ) - - // init SwapSink directing swapped funds to AutoBalancer - // - // Swaps provided Stable to YieldToken & deposits to the AutoBalancer - let abaSwapSink = SwapConnectors.SwapSink(swapper: stableToYieldSwapper, sink: abaSink, uniqueID: uniqueID) - // Swaps YieldToken & provides swapped Stable, sourcing YieldToken from the AutoBalancer - let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToStableSwapper, source: abaSource, uniqueID: uniqueID) - - // open a FlowCreditMarket position - let poolCap = FlowYieldVaultsStrategies.account.storage.load>( - from: FlowCreditMarket.PoolCapStoragePath - ) ?? panic("Missing pool capability") - - let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap") - - let pid = poolRef.createPosition( - funds: <-withFunds, - issuanceSink: abaSwapSink, - repaymentSource: abaSwapSource, - pushToDrawDownSink: true - ) - let position = FlowCreditMarket.Position(id: pid, pool: poolCap) - FlowYieldVaultsStrategies.account.storage.save(poolCap, to: FlowCreditMarket.PoolCapStoragePath) - - // get Sink & Source connectors relating to the new Position - let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true) - let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) // TODO: may need to be false - - // init YieldToken -> FLOW Swapper - let yieldToFlowSwapper = MockSwapper.Swapper( - inVault: yieldTokenType, - outVault: collateralType, - uniqueID: uniqueID - ) - // allows for YieldToken to be deposited to the Position - let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID) - - // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value, - // recollateralizing the position - autoBalancer.setSink(positionSwapSink, updateSinkID: true) - - // Use the same uniqueID passed to createStrategy so Strategy.burnCallback - // calls _cleanupAutoBalancer with the correct ID - return <-create TracerStrategy( - id: uniqueID, - collateralType: collateralType, - position: position - ) - } - } - - /// This strategy uses mUSDC vaults - access(all) resource mUSDCStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource { - /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol- - /// specific Identifier to associated connectors on construction - access(contract) var uniqueID: DeFiActions.UniqueIdentifier? - access(self) let position: FlowCreditMarket.Position - access(self) var sink: {DeFiActions.Sink} - access(self) var source: {DeFiActions.Source} - - init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowCreditMarket.Position) { - self.uniqueID = id - self.position = position - self.sink = position.createSink(type: collateralType) - self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) - } - - // Inherited from FlowYieldVaults.Strategy default implementation - // access(all) view fun isSupportedCollateralType(_ type: Type): Bool - - access(all) view fun getSupportedCollateralTypes(): {Type: Bool} { - return { self.sink.getSinkType(): true } - } - /// Returns the amount available for withdrawal via the inner Source - access(all) fun availableBalance(ofToken: Type): UFix64 { - return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0 - } - /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference - access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) { - self.sink.depositCapacity(from: from) - } - /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported, - /// an empty Vault is returned. - access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} { - if ofToken != self.source.getSourceType() { - return <- DeFiActionsUtils.getEmptyVault(ofToken) - } - return <- self.source.withdrawAvailable(maxAmount: maxAmount) - } - /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer - access(contract) fun burnCallback() { - FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!) - } - access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { - return DeFiActions.ComponentInfo( - type: self.getType(), - id: self.id(), - innerComponents: [ - self.sink.getComponentInfo(), - self.source.getComponentInfo() - ] - ) - } - access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? { - return self.uniqueID - } - access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) { - self.uniqueID = id - } - } - - /// This StrategyComposer builds a mUSDCStrategy - access(all) resource mUSDCStrategyComposer : FlowYieldVaults.StrategyComposer { - /// { Strategy Type: { Collateral Type: { String: AnyStruct } } } - access(self) let config: {Type: {Type: {String: AnyStruct}}} - - init(_ config: {Type: {Type: {String: AnyStruct}}}) { - self.config = config - } - - /// Returns the Types of Strategies composed by this StrategyComposer - access(all) view fun getComposedStrategyTypes(): {Type: Bool} { - let composed: {Type: Bool} = {} - for t in self.config.keys { - composed[t] = true - } - return composed - } - - /// Returns the Vault types which can be used to initialize a given Strategy - access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} { - let supported: {Type: Bool} = {} - if let strategyConfig = &self.config[forStrategy] as &{Type: {String: AnyStruct}}? { - for collateralType in strategyConfig.keys { - supported[collateralType] = true - } - } - return supported - } - - /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the - /// provided Vault type - access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} { - let supportedInitVaults = self.getSupportedInitializationVaults(forStrategy: forStrategy) - if supportedInitVaults[initializedWith] == true { - return { initializedWith: true } - } - return {} - } - - /// Composes a Strategy of the given type with the provided funds - /// TODO: Open up for multiple collateral types - access(all) fun createStrategy( - _ type: Type, - uniqueID: DeFiActions.UniqueIdentifier, - withFunds: @{FungibleToken.Vault} - ): @{FlowYieldVaults.Strategy} { - let collateralType = withFunds.getType() - let strategyConfig = self.config[type] - ?? panic("Could not find a config for Strategy \(type.identifier) initialized with \(collateralType.identifier)") - let collateralConfig = strategyConfig[collateralType] - ?? panic("Could not find config for collateral \(collateralType.identifier) when creating Strategy \(type.identifier)") - - // assign token types & associated EVM Addresses - let moetTokenType: Type = Type<@MOET.Vault>() - let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType) - ?? panic("Token Vault type \(moetTokenType.identifier) has not yet been registered with the VMbridge") - let yieldTokenEVMAddress = collateralConfig["yieldTokenEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"yieldTokenEVMAddress\" in config") - let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress) - ?? panic("Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())") - - // assign underlying asset EVM address & type - assumed to be stablecoin for the tracer strategy - let underlying4626AssetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress( - vault: yieldTokenEVMAddress - ) ?? panic("Could not get the underlying asset's EVM address for ERC4626Vault \(yieldTokenEVMAddress.toString())") - let underlying4626AssetType = FlowEVMBridgeConfig.getTypeAssociated(with: underlying4626AssetEVMAddress) - ?? panic("Could not retrieve the VM Bridge associated Type for the ERC4626 underlying asset \(underlying4626AssetEVMAddress.toString())") - - // create the oracle for the assets to be held in the AutoBalancer retrieving the NAV of the 4626 vault - let yieldTokenOracle = ERC4626PriceOracles.PriceOracle( - vault: yieldTokenEVMAddress, - asset: underlying4626AssetType, - // asset: moetTokenType, // TODO: make a composite oracle that returns the price denominated in MOET - uniqueID: uniqueID - ) - - // Create recurring config for automatic rebalancing - let recurringConfig = FlowYieldVaultsStrategies._createRecurringConfig(withID: uniqueID) - - // configure and AutoBalancer for this stack with native recurring scheduling - let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer( - oracle: yieldTokenOracle, // used to determine value of deposits & when to rebalance - vaultType: yieldTokenType, // the type of Vault held by the AutoBalancer - lowerThreshold: 0.95, // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits - upperThreshold: 1.05, // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits - rebalanceSink: nil, // nil on init - will be set once a PositionSink is available - rebalanceSource: nil, // nil on init - not set for TracerStrategy - recurringConfig: recurringConfig, // enables native AutoBalancer self-scheduling - uniqueID: uniqueID // identifies AutoBalancer as part of this Strategy - ) - // enables deposits of YieldToken to the AutoBalancer - let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") - // enables withdrawals of YieldToken from the AutoBalancer - let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") - - // create MOET <-> YIELD swappers - // - // get Uniswap V3 addresses from config - let univ3FactoryEVMAddress = collateralConfig["univ3FactoryEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3FactoryEVMAddress\" in config") - let univ3RouterEVMAddress = collateralConfig["univ3RouterEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3RouterEVMAddress\" in config") - let univ3QuoterEVMAddress = collateralConfig["univ3QuoterEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3QuoterEVMAddress\" in config") - // MOET -> YIELD - MOET can swap to YieldToken via two primary routes - // - via AMM swap pairing MOET <-> YIELD - // - via 4626 vault, swapping first to underlying asset then depositing to the 4626 vault - // MOET -> YIELD high-level Swapper then contains - // - MultiSwapper aggregates across two sub-swappers - // - MOET -> YIELD (UniV3 Swapper) - // - SequentialSwapper - // - MOET -> UNDERLYING (UniV3 Swapper) - // - UNDERLYING -> YIELD (ERC4626Swapper) - let moetToYieldAMMSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: univ3FactoryEVMAddress, - routerAddress: univ3RouterEVMAddress, - quoterAddress: univ3QuoterEVMAddress, - tokenPath: [moetTokenEVMAddress, yieldTokenEVMAddress], - feePath: [100], - inVault: moetTokenType, - outVault: yieldTokenType, - coaCapability: FlowYieldVaultsStrategies._getCOACapability(), - uniqueID: uniqueID - ) - // Swap MOET -> UNDERLYING via AMM - let moetToUnderlyingAssetSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: univ3FactoryEVMAddress, - routerAddress: univ3RouterEVMAddress, - quoterAddress: univ3QuoterEVMAddress, - tokenPath: [moetTokenEVMAddress, underlying4626AssetEVMAddress], - feePath: [100], - inVault: moetTokenType, - outVault: underlying4626AssetType, - coaCapability: FlowYieldVaultsStrategies._getCOACapability(), - uniqueID: uniqueID - ) - // Swap UNDERLYING -> YIELD via ERC4626 Vault - let underlyingTo4626Swapper = ERC4626SwapConnectors.Swapper( - asset: underlying4626AssetType, - vault: yieldTokenEVMAddress, - coa: FlowYieldVaultsStrategies._getCOACapability(), - feeSource: FlowYieldVaultsStrategies._createFeeSource(withID: uniqueID), - uniqueID: uniqueID - ) - // Compose v3 swapper & 4626 swapper into sequential swapper for MOET -> UNDERLYING -> YIELD - let moetToYieldSeqSwapper = SwapConnectors.SequentialSwapper( - swappers: [moetToUnderlyingAssetSwapper, underlyingTo4626Swapper], - uniqueID: uniqueID - ) - // Finally, add the two MOET -> YIELD swappers into an aggregate MultiSwapper - let moetToYieldSwapper = SwapConnectors.MultiSwapper( - inVault: moetTokenType, - outVault: yieldTokenType, - swappers: [moetToYieldAMMSwapper, moetToYieldSeqSwapper], - uniqueID: uniqueID - ) - - // YIELD -> MOET - // - Targets the MOET <-> YIELD pool as the only route since withdraws from the ERC4626 Vault are async - let yieldToMOETSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: univ3FactoryEVMAddress, - routerAddress: univ3RouterEVMAddress, - quoterAddress: univ3QuoterEVMAddress, - tokenPath: [yieldTokenEVMAddress, moetTokenEVMAddress], - feePath: [100], - inVault: yieldTokenType, - outVault: moetTokenType, - coaCapability: FlowYieldVaultsStrategies._getCOACapability(), - uniqueID: uniqueID - ) - - // init SwapSink directing swapped funds to AutoBalancer - // - // Swaps provided MOET to YIELD & deposits to the AutoBalancer - let abaSwapSink = SwapConnectors.SwapSink(swapper: moetToYieldSwapper, sink: abaSink, uniqueID: uniqueID) - // Swaps YIELD & provides swapped MOET, sourcing YIELD from the AutoBalancer - let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToMOETSwapper, source: abaSource, uniqueID: uniqueID) - - // open a FlowCreditMarket position - let poolCap = FlowYieldVaultsStrategies.account.storage.copy>( - from: FlowCreditMarket.PoolCapStoragePath - ) ?? panic("Missing or invalid pool capability") - let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap") - - let pid = poolRef.createPosition( - funds: <-withFunds, - issuanceSink: abaSwapSink, - repaymentSource: abaSwapSource, - pushToDrawDownSink: true - ) - let position = FlowCreditMarket.Position(id: pid, pool: poolCap) - - // get Sink & Source connectors relating to the new Position - let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true) - let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) - - // init YieldToken -> FLOW Swapper - // - // get UniswapV3 path configs - let collateralUniV3AddressPathConfig = collateralConfig["yieldToCollateralUniV3AddressPaths"] as? {Type: [EVM.EVMAddress]} - ?? panic("Could not find UniswapV3 address paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)") - let uniV3AddressPath = collateralUniV3AddressPathConfig[collateralType] - ?? panic("Could not find UniswapV3 address path for collateral type \(collateralType.identifier)") - assert(uniV3AddressPath.length > 1, message: "Invalid Uniswap V3 swap path length of \(uniV3AddressPath.length)") - assert(uniV3AddressPath[0].equals(yieldTokenEVMAddress), - message: "UniswapV3 swap path does not match - expected path[0] to be \(yieldTokenEVMAddress.toString()) but found \(uniV3AddressPath[0].toString())") - let collateralUniV3FeePathConfig = collateralConfig["yieldToCollateralUniV3FeePaths"] as? {Type: [UInt32]} - ?? panic("Could not find UniswapV3 fee paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)") - let uniV3FeePath = collateralUniV3FeePathConfig[collateralType] - ?? panic("Could not find UniswapV3 fee path for collateral type \(collateralType.identifier)") - assert(uniV3FeePath.length > 0, message: "Invalid Uniswap V3 fee path length of \(uniV3FeePath.length)") - // initialize the swapper used for recollateralization of the lending position as YIELD increases in value - let yieldToFlowSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: univ3FactoryEVMAddress, - routerAddress: univ3RouterEVMAddress, - quoterAddress: univ3QuoterEVMAddress, - tokenPath: uniV3AddressPath, - feePath: uniV3FeePath, - inVault: yieldTokenType, - outVault: collateralType, - coaCapability: FlowYieldVaultsStrategies._getCOACapability(), - uniqueID: uniqueID - ) - // allows for YIELD to be deposited to the Position as the collateral basis - let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID) - - // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value, recollateralizing - // the position - autoBalancer.setSink(positionSwapSink, updateSinkID: true) - - // Use the same uniqueID passed to createStrategy so Strategy.burnCallback - // calls _cleanupAutoBalancer with the correct ID - return <-create mUSDCStrategy( - id: uniqueID, - collateralType: collateralType, - position: position - ) - } - } - - access(all) entitlement Configure - - /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which - /// may utilize resource consumption (i.e. account storage). Since TracerStrategy creation consumes account storage - /// via configured AutoBalancers - access(all) resource StrategyComposerIssuer : FlowYieldVaults.StrategyComposerIssuer { - /// { StrategyComposer Type: { Strategy Type: { Collateral Type: { String: AnyStruct } } } } - access(all) let configs: {Type: {Type: {Type: {String: AnyStruct}}}} - - init(configs: {Type: {Type: {Type: {String: AnyStruct}}}}) { - self.configs = configs - } - - access(all) view fun getSupportedComposers(): {Type: Bool} { - return { - Type<@mUSDCStrategyComposer>(): true, - Type<@TracerStrategyComposer>(): true - } - } - access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} { - pre { - self.getSupportedComposers()[type] == true: - "Unsupported StrategyComposer \(type.identifier) requested" - (&self.configs[type] as &{Type: {Type: {String: AnyStruct}}}?) != nil: - "Could not find config for StrategyComposer \(type.identifier)" - } - switch type { - case Type<@mUSDCStrategyComposer>(): - return <- create mUSDCStrategyComposer(self.configs[type]!) - case Type<@TracerStrategyComposer>(): - return <- create TracerStrategyComposer() - default: - panic("Unsupported StrategyComposer \(type.identifier) requested") - } - } - access(Configure) fun upsertConfigFor(composer: Type, config: {Type: {Type: {String: AnyStruct}}}) { - pre { - self.getSupportedComposers()[composer] == true: - "Unsupported StrategyComposer Type \(composer.identifier)" - } - for stratType in config.keys { - assert(stratType.isSubtype(of: Type<@{FlowYieldVaults.Strategy}>()), - message: "Invalid config key \(stratType.identifier) - not a FlowYieldVaults.Strategy Type") - for collateralType in config[stratType]!.keys { - assert(collateralType.isSubtype(of: Type<@{FungibleToken.Vault}>()), - message: "Invalid config key at config[\(stratType.identifier)] - \(collateralType.identifier) is not a FungibleToken.Vault") - } - } - self.configs[composer] = config - } - } - - /// Returns the COA capability for this account - /// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors - access(self) - fun _getCOACapability(): Capability { - let coaCap = self.account.capabilities.storage.issue(/storage/evm) - assert(coaCap.check(), message: "Could not issue COA capability") - return coaCap - } - - /// Returns a FungibleTokenConnectors.VaultSinkAndSource used to subsidize cross VM token movement in contract- - /// defined strategies. - access(self) - fun _createFeeSource(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} { - let capPath = /storage/strategiesFeeSource - if self.account.storage.type(at: capPath) == nil { - let cap = self.account.capabilities.storage.issue(/storage/flowTokenVault) - self.account.storage.save(cap, to: capPath) - } - let vaultCap = self.account.storage.copy>(from: capPath) - ?? panic("Could not find fee source Capability at \(capPath)") - return FungibleTokenConnectors.VaultSinkAndSource( - min: nil, - max: nil, - vault: vaultCap, - uniqueID: withID - ) - } - - /// Creates an AutoBalancerRecurringConfig for scheduled rebalancing. - /// The txnFunder uses the contract's FlowToken vault to pay for scheduling fees. - access(self) - fun _createRecurringConfig(withID: DeFiActions.UniqueIdentifier?): DeFiActions.AutoBalancerRecurringConfig { - // Create txnFunder that can provide/accept FLOW for scheduling fees - let txnFunder = self._createTxnFunder(withID: withID) - - return DeFiActions.AutoBalancerRecurringConfig( - interval: 60 * 10, // Rebalance every 10 minutes - priority: FlowTransactionScheduler.Priority.Medium, - executionEffort: 999, - forceRebalance: false, - txnFunder: txnFunder - ) - } - - /// Creates a Sink+Source for the AutoBalancer to use for scheduling fees - access(self) - fun _createTxnFunder(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} { - let capPath = /storage/autoBalancerTxnFunder - if self.account.storage.type(at: capPath) == nil { - let cap = self.account.capabilities.storage.issue(/storage/flowTokenVault) - self.account.storage.save(cap, to: capPath) - } - let vaultCap = self.account.storage.copy>(from: capPath) - ?? panic("Could not find txnFunder Capability at \(capPath)") - return FungibleTokenConnectors.VaultSinkAndSource( - min: nil, - max: nil, - vault: vaultCap, - uniqueID: withID - ) - } - - init( - univ3FactoryEVMAddress: String, - univ3RouterEVMAddress: String, - univ3QuoterEVMAddress: String, - yieldTokenEVMAddress: String, - recollateralizationUniV3AddressPath: [String], - recollateralizationUniV3FeePath: [UInt32], - ) { - self.univ3FactoryEVMAddress = EVM.addressFromString(univ3FactoryEVMAddress) - self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress) - self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress) - self.yieldTokenEVMAddress = EVM.addressFromString(yieldTokenEVMAddress) - self.IssuerStoragePath = StoragePath(identifier: "FlowYieldVaultsStrategyComposerIssuer_\(self.account.address)")! - - let initialCollateralType = Type<@FlowToken.Vault>() - let moetType = Type<@MOET.Vault>() - let moetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>()) - ?? panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge") - let yieldTokenEVMAddress = EVM.addressFromString(yieldTokenEVMAddress) - - let swapAddressPath: [EVM.EVMAddress] = [] - for hex in recollateralizationUniV3AddressPath { - swapAddressPath.append(EVM.addressFromString(hex)) - } - - let configs: {Type: {Type: {Type: {String: AnyStruct}}}} = { - Type<@mUSDCStrategyComposer>(): { - Type<@mUSDCStrategy>(): { - initialCollateralType: { - "univ3FactoryEVMAddress": self.univ3FactoryEVMAddress, - "univ3RouterEVMAddress": self.univ3RouterEVMAddress, - "univ3QuoterEVMAddress": self.univ3QuoterEVMAddress, - "yieldTokenEVMAddress": self.yieldTokenEVMAddress, - "yieldToCollateralUniV3AddressPaths": { - initialCollateralType: swapAddressPath - }, - "yieldToCollateralUniV3FeePaths": { - initialCollateralType: recollateralizationUniV3FeePath - } - } - } - }, - Type<@TracerStrategyComposer>(): { - Type<@TracerStrategy>(): {} - } - } - self.account.storage.save(<-create StrategyComposerIssuer(configs: configs), to: self.IssuerStoragePath) - - // TODO: this is temporary until we have a better way to pass user's COAs to inner connectors - // create a COA in this account - if self.account.storage.type(at: /storage/evm) == nil { - self.account.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) - let cap = self.account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(/storage/evm) - self.account.capabilities.publish(cap, at: /public/evm) - } - } -} From 2d4e12932cb542ead9368d02a87aeea8118456d4 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 22 Dec 2025 13:38:14 -0500 Subject: [PATCH 14/31] fix schedule time --- cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index aac45b3..24de3dc 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -554,7 +554,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { let txnFunder = self._createTxnFunder(withID: withID) return DeFiActions.AutoBalancerRecurringConfig( - interval: 60, // Rebalance every 60 seconds + interval: 60 * 10, // Rebalance every 10 minutes priority: FlowTransactionScheduler.Priority.Medium, executionEffort: 800, forceRebalance: false, From 976fee8dd2ee55e387dd76334c80957808ce0414 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 22 Dec 2025 13:51:59 -0500 Subject: [PATCH 15/31] Revert "temporarily delete strategy" This reverts commit 7d6c2bb45b1aefac26fd21f9ff826c0a0bda4064. --- .../contracts/FlowYieldVaultsStrategies.cdc | 705 ++++++++++++++++++ 1 file changed, 705 insertions(+) create mode 100644 cadence/contracts/FlowYieldVaultsStrategies.cdc diff --git a/cadence/contracts/FlowYieldVaultsStrategies.cdc b/cadence/contracts/FlowYieldVaultsStrategies.cdc new file mode 100644 index 0000000..f585956 --- /dev/null +++ b/cadence/contracts/FlowYieldVaultsStrategies.cdc @@ -0,0 +1,705 @@ +// standards +import "FungibleToken" +import "FlowToken" +import "EVM" +// DeFiActions +import "DeFiActionsUtils" +import "DeFiActions" +import "SwapConnectors" +import "FungibleTokenConnectors" +// amm integration +import "UniswapV3SwapConnectors" +import "ERC4626SwapConnectors" +import "ERC4626Utils" +// Lending protocol +import "FlowCreditMarket" +// FlowYieldVaults platform +import "FlowYieldVaultsClosedBeta" +import "FlowYieldVaults" +import "FlowYieldVaultsAutoBalancers" +// scheduler +import "FlowTransactionScheduler" +import "FlowYieldVaultsSchedulerRegistry" +// tokens +import "YieldToken" +import "MOET" +// vm bridge +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +// live oracles +import "ERC4626PriceOracles" +// mocks +import "MockOracle" +import "MockSwapper" + +/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION +/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +/// +/// FlowYieldVaultsStrategies +/// +/// This contract defines Strategies used in the FlowYieldVaults platform. +/// +/// A Strategy instance can be thought of as objects wrapping a stack of DeFiActions connectors wired together to +/// (optimally) generate some yield on initial deposits. Strategies can be simple such as swapping into a yield-bearing +/// asset (such as stFLOW) or more complex DeFiActions stacks. +/// +/// A StrategyComposer is tasked with the creation of a supported Strategy. It's within the stacking of DeFiActions +/// connectors that the true power of the components lies. +/// +access(all) contract FlowYieldVaultsStrategies { + + access(all) let univ3FactoryEVMAddress: EVM.EVMAddress + access(all) let univ3RouterEVMAddress: EVM.EVMAddress + access(all) let univ3QuoterEVMAddress: EVM.EVMAddress + access(all) let yieldTokenEVMAddress: EVM.EVMAddress + + /// Canonical StoragePath where the StrategyComposerIssuer should be stored + access(all) let IssuerStoragePath: StoragePath + + /// This is the first Strategy implementation, wrapping a FlowCreditMarket Position along with its related Sink & + /// Source. While this object is a simple wrapper for the top-level collateralized position, the true magic of the + /// DeFiActions is in the stacking of the related connectors. This stacking logic can be found in the + /// TracerStrategyComposer construct. + access(all) resource TracerStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource { + /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol- + /// specific Identifier to associated connectors on construction + access(contract) var uniqueID: DeFiActions.UniqueIdentifier? + access(self) let position: FlowCreditMarket.Position + access(self) var sink: {DeFiActions.Sink} + access(self) var source: {DeFiActions.Source} + + init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowCreditMarket.Position) { + self.uniqueID = id + self.position = position + self.sink = position.createSink(type: collateralType) + self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) + } + + // Inherited from FlowYieldVaults.Strategy default implementation + // access(all) view fun isSupportedCollateralType(_ type: Type): Bool + + access(all) view fun getSupportedCollateralTypes(): {Type: Bool} { + return { self.sink.getSinkType(): true } + } + /// Returns the amount available for withdrawal via the inner Source + access(all) fun availableBalance(ofToken: Type): UFix64 { + return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0 + } + /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference + access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) { + self.sink.depositCapacity(from: from) + } + /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported, + /// an empty Vault is returned. + access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} { + if ofToken != self.source.getSourceType() { + return <- DeFiActionsUtils.getEmptyVault(ofToken) + } + return <- self.source.withdrawAvailable(maxAmount: maxAmount) + } + /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer + access(contract) fun burnCallback() { + FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!) + } + access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { + return DeFiActions.ComponentInfo( + type: self.getType(), + id: self.id(), + innerComponents: [] + ) + } + access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? { + return self.uniqueID + } + access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) { + self.uniqueID = id + } + } + + /// This StrategyComposer builds a TracerStrategy + access(all) resource TracerStrategyComposer : FlowYieldVaults.StrategyComposer { + /// Returns the Types of Strategies composed by this StrategyComposer + access(all) view fun getComposedStrategyTypes(): {Type: Bool} { + return { Type<@TracerStrategy>(): true } + } + + /// Returns the Vault types which can be used to initialize a given Strategy + access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} { + return { Type<@FlowToken.Vault>(): true } + } + + /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the + /// provided Vault type + access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} { + return { Type<@FlowToken.Vault>(): true } + } + + /// Composes a Strategy of the given type with the provided funds + access(all) fun createStrategy( + _ type: Type, + uniqueID: DeFiActions.UniqueIdentifier, + withFunds: @{FungibleToken.Vault} + ): @{FlowYieldVaults.Strategy} { + // this PriceOracle is mocked and will be shared by all components used in the TracerStrategy + // TODO: add ERC4626 price oracle + let oracle = MockOracle.PriceOracle() + + // assign token types + + let moetTokenType: Type = Type<@MOET.Vault>() + let yieldTokenType = Type<@YieldToken.Vault>() + // assign collateral & flow token types + let collateralType = withFunds.getType() + + // Create recurring config for automatic rebalancing + let recurringConfig = FlowYieldVaultsStrategies._createRecurringConfig(withID: uniqueID) + + // configure and AutoBalancer for this stack with native recurring scheduling + let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer( + oracle: oracle, // used to determine value of deposits & when to rebalance + vaultType: yieldTokenType, // the type of Vault held by the AutoBalancer + lowerThreshold: 0.95, // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits + upperThreshold: 1.05, // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits + rebalanceSink: nil, // nil on init - will be set once a PositionSink is available + rebalanceSource: nil, // nil on init - not set for TracerStrategy + recurringConfig: recurringConfig, // enables native AutoBalancer self-scheduling + uniqueID: uniqueID // identifies AutoBalancer as part of this Strategy + ) + // enables deposits of YieldToken to the AutoBalancer + let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") + // enables withdrawals of YieldToken from the AutoBalancer + let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") + + // init Stable <> YIELD swappers + // + // Stable -> YieldToken + let stableToYieldSwapper = MockSwapper.Swapper( + inVault: moetTokenType, + outVault: yieldTokenType, + uniqueID: uniqueID + ) + // YieldToken -> Stable + let yieldToStableSwapper = MockSwapper.Swapper( + inVault: yieldTokenType, + outVault: moetTokenType, + uniqueID: uniqueID + ) + + // init SwapSink directing swapped funds to AutoBalancer + // + // Swaps provided Stable to YieldToken & deposits to the AutoBalancer + let abaSwapSink = SwapConnectors.SwapSink(swapper: stableToYieldSwapper, sink: abaSink, uniqueID: uniqueID) + // Swaps YieldToken & provides swapped Stable, sourcing YieldToken from the AutoBalancer + let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToStableSwapper, source: abaSource, uniqueID: uniqueID) + + // open a FlowCreditMarket position + let poolCap = FlowYieldVaultsStrategies.account.storage.load>( + from: FlowCreditMarket.PoolCapStoragePath + ) ?? panic("Missing pool capability") + + let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap") + + let pid = poolRef.createPosition( + funds: <-withFunds, + issuanceSink: abaSwapSink, + repaymentSource: abaSwapSource, + pushToDrawDownSink: true + ) + let position = FlowCreditMarket.Position(id: pid, pool: poolCap) + FlowYieldVaultsStrategies.account.storage.save(poolCap, to: FlowCreditMarket.PoolCapStoragePath) + + // get Sink & Source connectors relating to the new Position + let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true) + let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) // TODO: may need to be false + + // init YieldToken -> FLOW Swapper + let yieldToFlowSwapper = MockSwapper.Swapper( + inVault: yieldTokenType, + outVault: collateralType, + uniqueID: uniqueID + ) + // allows for YieldToken to be deposited to the Position + let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID) + + // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value, + // recollateralizing the position + autoBalancer.setSink(positionSwapSink, updateSinkID: true) + + // Use the same uniqueID passed to createStrategy so Strategy.burnCallback + // calls _cleanupAutoBalancer with the correct ID + return <-create TracerStrategy( + id: uniqueID, + collateralType: collateralType, + position: position + ) + } + } + + /// This strategy uses mUSDC vaults + access(all) resource mUSDCStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource { + /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol- + /// specific Identifier to associated connectors on construction + access(contract) var uniqueID: DeFiActions.UniqueIdentifier? + access(self) let position: FlowCreditMarket.Position + access(self) var sink: {DeFiActions.Sink} + access(self) var source: {DeFiActions.Source} + + init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowCreditMarket.Position) { + self.uniqueID = id + self.position = position + self.sink = position.createSink(type: collateralType) + self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) + } + + // Inherited from FlowYieldVaults.Strategy default implementation + // access(all) view fun isSupportedCollateralType(_ type: Type): Bool + + access(all) view fun getSupportedCollateralTypes(): {Type: Bool} { + return { self.sink.getSinkType(): true } + } + /// Returns the amount available for withdrawal via the inner Source + access(all) fun availableBalance(ofToken: Type): UFix64 { + return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0 + } + /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference + access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) { + self.sink.depositCapacity(from: from) + } + /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported, + /// an empty Vault is returned. + access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} { + if ofToken != self.source.getSourceType() { + return <- DeFiActionsUtils.getEmptyVault(ofToken) + } + return <- self.source.withdrawAvailable(maxAmount: maxAmount) + } + /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer + access(contract) fun burnCallback() { + FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!) + } + access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { + return DeFiActions.ComponentInfo( + type: self.getType(), + id: self.id(), + innerComponents: [ + self.sink.getComponentInfo(), + self.source.getComponentInfo() + ] + ) + } + access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? { + return self.uniqueID + } + access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) { + self.uniqueID = id + } + } + + /// This StrategyComposer builds a mUSDCStrategy + access(all) resource mUSDCStrategyComposer : FlowYieldVaults.StrategyComposer { + /// { Strategy Type: { Collateral Type: { String: AnyStruct } } } + access(self) let config: {Type: {Type: {String: AnyStruct}}} + + init(_ config: {Type: {Type: {String: AnyStruct}}}) { + self.config = config + } + + /// Returns the Types of Strategies composed by this StrategyComposer + access(all) view fun getComposedStrategyTypes(): {Type: Bool} { + let composed: {Type: Bool} = {} + for t in self.config.keys { + composed[t] = true + } + return composed + } + + /// Returns the Vault types which can be used to initialize a given Strategy + access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} { + let supported: {Type: Bool} = {} + if let strategyConfig = &self.config[forStrategy] as &{Type: {String: AnyStruct}}? { + for collateralType in strategyConfig.keys { + supported[collateralType] = true + } + } + return supported + } + + /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the + /// provided Vault type + access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} { + let supportedInitVaults = self.getSupportedInitializationVaults(forStrategy: forStrategy) + if supportedInitVaults[initializedWith] == true { + return { initializedWith: true } + } + return {} + } + + /// Composes a Strategy of the given type with the provided funds + /// TODO: Open up for multiple collateral types + access(all) fun createStrategy( + _ type: Type, + uniqueID: DeFiActions.UniqueIdentifier, + withFunds: @{FungibleToken.Vault} + ): @{FlowYieldVaults.Strategy} { + let collateralType = withFunds.getType() + let strategyConfig = self.config[type] + ?? panic("Could not find a config for Strategy \(type.identifier) initialized with \(collateralType.identifier)") + let collateralConfig = strategyConfig[collateralType] + ?? panic("Could not find config for collateral \(collateralType.identifier) when creating Strategy \(type.identifier)") + + // assign token types & associated EVM Addresses + let moetTokenType: Type = Type<@MOET.Vault>() + let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType) + ?? panic("Token Vault type \(moetTokenType.identifier) has not yet been registered with the VMbridge") + let yieldTokenEVMAddress = collateralConfig["yieldTokenEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"yieldTokenEVMAddress\" in config") + let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress) + ?? panic("Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())") + + // assign underlying asset EVM address & type - assumed to be stablecoin for the tracer strategy + let underlying4626AssetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress( + vault: yieldTokenEVMAddress + ) ?? panic("Could not get the underlying asset's EVM address for ERC4626Vault \(yieldTokenEVMAddress.toString())") + let underlying4626AssetType = FlowEVMBridgeConfig.getTypeAssociated(with: underlying4626AssetEVMAddress) + ?? panic("Could not retrieve the VM Bridge associated Type for the ERC4626 underlying asset \(underlying4626AssetEVMAddress.toString())") + + // create the oracle for the assets to be held in the AutoBalancer retrieving the NAV of the 4626 vault + let yieldTokenOracle = ERC4626PriceOracles.PriceOracle( + vault: yieldTokenEVMAddress, + asset: underlying4626AssetType, + // asset: moetTokenType, // TODO: make a composite oracle that returns the price denominated in MOET + uniqueID: uniqueID + ) + + // Create recurring config for automatic rebalancing + let recurringConfig = FlowYieldVaultsStrategies._createRecurringConfig(withID: uniqueID) + + // configure and AutoBalancer for this stack with native recurring scheduling + let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer( + oracle: yieldTokenOracle, // used to determine value of deposits & when to rebalance + vaultType: yieldTokenType, // the type of Vault held by the AutoBalancer + lowerThreshold: 0.95, // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits + upperThreshold: 1.05, // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits + rebalanceSink: nil, // nil on init - will be set once a PositionSink is available + rebalanceSource: nil, // nil on init - not set for TracerStrategy + recurringConfig: recurringConfig, // enables native AutoBalancer self-scheduling + uniqueID: uniqueID // identifies AutoBalancer as part of this Strategy + ) + // enables deposits of YieldToken to the AutoBalancer + let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") + // enables withdrawals of YieldToken from the AutoBalancer + let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") + + // create MOET <-> YIELD swappers + // + // get Uniswap V3 addresses from config + let univ3FactoryEVMAddress = collateralConfig["univ3FactoryEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3FactoryEVMAddress\" in config") + let univ3RouterEVMAddress = collateralConfig["univ3RouterEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3RouterEVMAddress\" in config") + let univ3QuoterEVMAddress = collateralConfig["univ3QuoterEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3QuoterEVMAddress\" in config") + // MOET -> YIELD - MOET can swap to YieldToken via two primary routes + // - via AMM swap pairing MOET <-> YIELD + // - via 4626 vault, swapping first to underlying asset then depositing to the 4626 vault + // MOET -> YIELD high-level Swapper then contains + // - MultiSwapper aggregates across two sub-swappers + // - MOET -> YIELD (UniV3 Swapper) + // - SequentialSwapper + // - MOET -> UNDERLYING (UniV3 Swapper) + // - UNDERLYING -> YIELD (ERC4626Swapper) + let moetToYieldAMMSwapper = UniswapV3SwapConnectors.Swapper( + factoryAddress: univ3FactoryEVMAddress, + routerAddress: univ3RouterEVMAddress, + quoterAddress: univ3QuoterEVMAddress, + tokenPath: [moetTokenEVMAddress, yieldTokenEVMAddress], + feePath: [100], + inVault: moetTokenType, + outVault: yieldTokenType, + coaCapability: FlowYieldVaultsStrategies._getCOACapability(), + uniqueID: uniqueID + ) + // Swap MOET -> UNDERLYING via AMM + let moetToUnderlyingAssetSwapper = UniswapV3SwapConnectors.Swapper( + factoryAddress: univ3FactoryEVMAddress, + routerAddress: univ3RouterEVMAddress, + quoterAddress: univ3QuoterEVMAddress, + tokenPath: [moetTokenEVMAddress, underlying4626AssetEVMAddress], + feePath: [100], + inVault: moetTokenType, + outVault: underlying4626AssetType, + coaCapability: FlowYieldVaultsStrategies._getCOACapability(), + uniqueID: uniqueID + ) + // Swap UNDERLYING -> YIELD via ERC4626 Vault + let underlyingTo4626Swapper = ERC4626SwapConnectors.Swapper( + asset: underlying4626AssetType, + vault: yieldTokenEVMAddress, + coa: FlowYieldVaultsStrategies._getCOACapability(), + feeSource: FlowYieldVaultsStrategies._createFeeSource(withID: uniqueID), + uniqueID: uniqueID + ) + // Compose v3 swapper & 4626 swapper into sequential swapper for MOET -> UNDERLYING -> YIELD + let moetToYieldSeqSwapper = SwapConnectors.SequentialSwapper( + swappers: [moetToUnderlyingAssetSwapper, underlyingTo4626Swapper], + uniqueID: uniqueID + ) + // Finally, add the two MOET -> YIELD swappers into an aggregate MultiSwapper + let moetToYieldSwapper = SwapConnectors.MultiSwapper( + inVault: moetTokenType, + outVault: yieldTokenType, + swappers: [moetToYieldAMMSwapper, moetToYieldSeqSwapper], + uniqueID: uniqueID + ) + + // YIELD -> MOET + // - Targets the MOET <-> YIELD pool as the only route since withdraws from the ERC4626 Vault are async + let yieldToMOETSwapper = UniswapV3SwapConnectors.Swapper( + factoryAddress: univ3FactoryEVMAddress, + routerAddress: univ3RouterEVMAddress, + quoterAddress: univ3QuoterEVMAddress, + tokenPath: [yieldTokenEVMAddress, moetTokenEVMAddress], + feePath: [100], + inVault: yieldTokenType, + outVault: moetTokenType, + coaCapability: FlowYieldVaultsStrategies._getCOACapability(), + uniqueID: uniqueID + ) + + // init SwapSink directing swapped funds to AutoBalancer + // + // Swaps provided MOET to YIELD & deposits to the AutoBalancer + let abaSwapSink = SwapConnectors.SwapSink(swapper: moetToYieldSwapper, sink: abaSink, uniqueID: uniqueID) + // Swaps YIELD & provides swapped MOET, sourcing YIELD from the AutoBalancer + let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToMOETSwapper, source: abaSource, uniqueID: uniqueID) + + // open a FlowCreditMarket position + let poolCap = FlowYieldVaultsStrategies.account.storage.copy>( + from: FlowCreditMarket.PoolCapStoragePath + ) ?? panic("Missing or invalid pool capability") + let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap") + + let pid = poolRef.createPosition( + funds: <-withFunds, + issuanceSink: abaSwapSink, + repaymentSource: abaSwapSource, + pushToDrawDownSink: true + ) + let position = FlowCreditMarket.Position(id: pid, pool: poolCap) + + // get Sink & Source connectors relating to the new Position + let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true) + let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) + + // init YieldToken -> FLOW Swapper + // + // get UniswapV3 path configs + let collateralUniV3AddressPathConfig = collateralConfig["yieldToCollateralUniV3AddressPaths"] as? {Type: [EVM.EVMAddress]} + ?? panic("Could not find UniswapV3 address paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)") + let uniV3AddressPath = collateralUniV3AddressPathConfig[collateralType] + ?? panic("Could not find UniswapV3 address path for collateral type \(collateralType.identifier)") + assert(uniV3AddressPath.length > 1, message: "Invalid Uniswap V3 swap path length of \(uniV3AddressPath.length)") + assert(uniV3AddressPath[0].equals(yieldTokenEVMAddress), + message: "UniswapV3 swap path does not match - expected path[0] to be \(yieldTokenEVMAddress.toString()) but found \(uniV3AddressPath[0].toString())") + let collateralUniV3FeePathConfig = collateralConfig["yieldToCollateralUniV3FeePaths"] as? {Type: [UInt32]} + ?? panic("Could not find UniswapV3 fee paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)") + let uniV3FeePath = collateralUniV3FeePathConfig[collateralType] + ?? panic("Could not find UniswapV3 fee path for collateral type \(collateralType.identifier)") + assert(uniV3FeePath.length > 0, message: "Invalid Uniswap V3 fee path length of \(uniV3FeePath.length)") + // initialize the swapper used for recollateralization of the lending position as YIELD increases in value + let yieldToFlowSwapper = UniswapV3SwapConnectors.Swapper( + factoryAddress: univ3FactoryEVMAddress, + routerAddress: univ3RouterEVMAddress, + quoterAddress: univ3QuoterEVMAddress, + tokenPath: uniV3AddressPath, + feePath: uniV3FeePath, + inVault: yieldTokenType, + outVault: collateralType, + coaCapability: FlowYieldVaultsStrategies._getCOACapability(), + uniqueID: uniqueID + ) + // allows for YIELD to be deposited to the Position as the collateral basis + let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID) + + // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value, recollateralizing + // the position + autoBalancer.setSink(positionSwapSink, updateSinkID: true) + + // Use the same uniqueID passed to createStrategy so Strategy.burnCallback + // calls _cleanupAutoBalancer with the correct ID + return <-create mUSDCStrategy( + id: uniqueID, + collateralType: collateralType, + position: position + ) + } + } + + access(all) entitlement Configure + + /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which + /// may utilize resource consumption (i.e. account storage). Since TracerStrategy creation consumes account storage + /// via configured AutoBalancers + access(all) resource StrategyComposerIssuer : FlowYieldVaults.StrategyComposerIssuer { + /// { StrategyComposer Type: { Strategy Type: { Collateral Type: { String: AnyStruct } } } } + access(all) let configs: {Type: {Type: {Type: {String: AnyStruct}}}} + + init(configs: {Type: {Type: {Type: {String: AnyStruct}}}}) { + self.configs = configs + } + + access(all) view fun getSupportedComposers(): {Type: Bool} { + return { + Type<@mUSDCStrategyComposer>(): true, + Type<@TracerStrategyComposer>(): true + } + } + access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} { + pre { + self.getSupportedComposers()[type] == true: + "Unsupported StrategyComposer \(type.identifier) requested" + (&self.configs[type] as &{Type: {Type: {String: AnyStruct}}}?) != nil: + "Could not find config for StrategyComposer \(type.identifier)" + } + switch type { + case Type<@mUSDCStrategyComposer>(): + return <- create mUSDCStrategyComposer(self.configs[type]!) + case Type<@TracerStrategyComposer>(): + return <- create TracerStrategyComposer() + default: + panic("Unsupported StrategyComposer \(type.identifier) requested") + } + } + access(Configure) fun upsertConfigFor(composer: Type, config: {Type: {Type: {String: AnyStruct}}}) { + pre { + self.getSupportedComposers()[composer] == true: + "Unsupported StrategyComposer Type \(composer.identifier)" + } + for stratType in config.keys { + assert(stratType.isSubtype(of: Type<@{FlowYieldVaults.Strategy}>()), + message: "Invalid config key \(stratType.identifier) - not a FlowYieldVaults.Strategy Type") + for collateralType in config[stratType]!.keys { + assert(collateralType.isSubtype(of: Type<@{FungibleToken.Vault}>()), + message: "Invalid config key at config[\(stratType.identifier)] - \(collateralType.identifier) is not a FungibleToken.Vault") + } + } + self.configs[composer] = config + } + } + + /// Returns the COA capability for this account + /// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors + access(self) + fun _getCOACapability(): Capability { + let coaCap = self.account.capabilities.storage.issue(/storage/evm) + assert(coaCap.check(), message: "Could not issue COA capability") + return coaCap + } + + /// Returns a FungibleTokenConnectors.VaultSinkAndSource used to subsidize cross VM token movement in contract- + /// defined strategies. + access(self) + fun _createFeeSource(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} { + let capPath = /storage/strategiesFeeSource + if self.account.storage.type(at: capPath) == nil { + let cap = self.account.capabilities.storage.issue(/storage/flowTokenVault) + self.account.storage.save(cap, to: capPath) + } + let vaultCap = self.account.storage.copy>(from: capPath) + ?? panic("Could not find fee source Capability at \(capPath)") + return FungibleTokenConnectors.VaultSinkAndSource( + min: nil, + max: nil, + vault: vaultCap, + uniqueID: withID + ) + } + + /// Creates an AutoBalancerRecurringConfig for scheduled rebalancing. + /// The txnFunder uses the contract's FlowToken vault to pay for scheduling fees. + access(self) + fun _createRecurringConfig(withID: DeFiActions.UniqueIdentifier?): DeFiActions.AutoBalancerRecurringConfig { + // Create txnFunder that can provide/accept FLOW for scheduling fees + let txnFunder = self._createTxnFunder(withID: withID) + + return DeFiActions.AutoBalancerRecurringConfig( + interval: 60 * 10, // Rebalance every 10 minutes + priority: FlowTransactionScheduler.Priority.Medium, + executionEffort: 999, + forceRebalance: false, + txnFunder: txnFunder + ) + } + + /// Creates a Sink+Source for the AutoBalancer to use for scheduling fees + access(self) + fun _createTxnFunder(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} { + let capPath = /storage/autoBalancerTxnFunder + if self.account.storage.type(at: capPath) == nil { + let cap = self.account.capabilities.storage.issue(/storage/flowTokenVault) + self.account.storage.save(cap, to: capPath) + } + let vaultCap = self.account.storage.copy>(from: capPath) + ?? panic("Could not find txnFunder Capability at \(capPath)") + return FungibleTokenConnectors.VaultSinkAndSource( + min: nil, + max: nil, + vault: vaultCap, + uniqueID: withID + ) + } + + init( + univ3FactoryEVMAddress: String, + univ3RouterEVMAddress: String, + univ3QuoterEVMAddress: String, + yieldTokenEVMAddress: String, + recollateralizationUniV3AddressPath: [String], + recollateralizationUniV3FeePath: [UInt32], + ) { + self.univ3FactoryEVMAddress = EVM.addressFromString(univ3FactoryEVMAddress) + self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress) + self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress) + self.yieldTokenEVMAddress = EVM.addressFromString(yieldTokenEVMAddress) + self.IssuerStoragePath = StoragePath(identifier: "FlowYieldVaultsStrategyComposerIssuer_\(self.account.address)")! + + let initialCollateralType = Type<@FlowToken.Vault>() + let moetType = Type<@MOET.Vault>() + let moetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>()) + ?? panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge") + let yieldTokenEVMAddress = EVM.addressFromString(yieldTokenEVMAddress) + + let swapAddressPath: [EVM.EVMAddress] = [] + for hex in recollateralizationUniV3AddressPath { + swapAddressPath.append(EVM.addressFromString(hex)) + } + + let configs: {Type: {Type: {Type: {String: AnyStruct}}}} = { + Type<@mUSDCStrategyComposer>(): { + Type<@mUSDCStrategy>(): { + initialCollateralType: { + "univ3FactoryEVMAddress": self.univ3FactoryEVMAddress, + "univ3RouterEVMAddress": self.univ3RouterEVMAddress, + "univ3QuoterEVMAddress": self.univ3QuoterEVMAddress, + "yieldTokenEVMAddress": self.yieldTokenEVMAddress, + "yieldToCollateralUniV3AddressPaths": { + initialCollateralType: swapAddressPath + }, + "yieldToCollateralUniV3FeePaths": { + initialCollateralType: recollateralizationUniV3FeePath + } + } + } + }, + Type<@TracerStrategyComposer>(): { + Type<@TracerStrategy>(): {} + } + } + self.account.storage.save(<-create StrategyComposerIssuer(configs: configs), to: self.IssuerStoragePath) + + // TODO: this is temporary until we have a better way to pass user's COAs to inner connectors + // create a COA in this account + if self.account.storage.type(at: /storage/evm) == nil { + self.account.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) + let cap = self.account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(/storage/evm) + self.account.capabilities.publish(cap, at: /public/evm) + } + } +} From d201f9174f7b5d1d4c87a6ed64e9c06066489170 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:11:04 -0500 Subject: [PATCH 16/31] update env vars --- local/punchswap/punchswap.env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/local/punchswap/punchswap.env b/local/punchswap/punchswap.env index 04f6668..345e91a 100644 --- a/local/punchswap/punchswap.env +++ b/local/punchswap/punchswap.env @@ -30,8 +30,8 @@ TOKENS_OWNER=0xC31A5268a1d311d992D637E8cE925bfdcCEB4310 USDC_MINT=2000000000000 WBTC_MINT=100000000000000 -USDC_ADDR=0x102A7ed67858cF757CBBeA3390eaB72fcc60237E -WBTC_ADDR=0x6Ce75363856e46B42bf0c933E01A4Eec17eE963B +USDC_ADDR=0xaf2Bb3E0017d2eD9B69004c9b38318a166fA9f63 +WBTC_ADDR=0x34A99426966D2023f1f66929c1b0989fA5C6B9FC # how much to fund the helper (base units) # USDC_FUND=600000000 # 600k * 1e6 From 244209a097f8cb004e9ea0783c7740273221b43e Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:50:55 -0500 Subject: [PATCH 17/31] add missing files --- .../admin/upsert_musdf_config.cdc | 57 +++++++++++++++++++ flow.json | 8 +++ local/setup_mainnet.sh | 32 ++++++++++- 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc diff --git a/cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc b/cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc new file mode 100644 index 0000000..74ff5c1 --- /dev/null +++ b/cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc @@ -0,0 +1,57 @@ +import "FungibleToken" +import "EVM" +import "FlowYieldVaultsStrategiesV1_1" + +/// Admin tx to (re)configure Uniswap paths for the mUSDFStrategy +/// +/// NOTE: +/// - Must be signed by the account that deployed FlowYieldVaultsStrategies +/// - You can omit some collaterals by passing empty arrays and guarding in prepare{} +transaction( + tokenTypeIdentifier: String, + yieldTokenEVMAddress: String, + + // collateral path/fees: [YIELD, ..., ] + swapPath: [String], + fees: [UInt32] +) { + + prepare(acct: auth(Storage, Capabilities, BorrowValue) &Account) { + let tokenType = CompositeType(tokenTypeIdentifier) + ?? panic("Invalid tokenTypeIdentifier \(tokenTypeIdentifier)") + // This tx must run on the same account that stores the issuer + // otherwise this borrow will fail. + let issuer = acct.storage.borrow< + auth(FlowYieldVaultsStrategiesV1_1.Configure) &FlowYieldVaultsStrategiesV1_1.StrategyComposerIssuer + >(from: FlowYieldVaultsStrategiesV1_1.IssuerStoragePath) + ?? panic("Missing StrategyComposerIssuer at IssuerStoragePath") + + let yieldEVM = EVM.addressFromString(yieldTokenEVMAddress) + + // helper to map [String] -> [EVM.EVMAddress] + fun toEVM(_ hexes: [String]): [EVM.EVMAddress] { + let out: [EVM.EVMAddress] = [] + for h in hexes { + out.append(EVM.addressFromString(h)) + } + return out + } + + let composerType = Type<@FlowYieldVaultsStrategiesV1_1.mUSDFStrategyComposer>() + let strategyType = Type<@FlowYieldVaultsStrategiesV1_1.mUSDFStrategy>() + + //issuer.purgeConfig() + + // === FLOW collateral === + if swapPath.length > 0 { + issuer.addOrUpdateCollateralConfig( + composer: composerType, + strategyType: strategyType, + collateralVaultType: tokenType, + yieldTokenEVMAddress: yieldEVM, + yieldToCollateralAddressPath: toEVM(swapPath), + yieldToCollateralFeePath: fees + ) + } + } +} diff --git a/flow.json b/flow.json index 8854daf..523fbc0 100644 --- a/flow.json +++ b/flow.json @@ -737,6 +737,14 @@ "resourceID": "projects/dl-flow-devex-production/locations/us-central1/keyRings/tidal-keyring/cryptoKeys/tidal_admin_pk/cryptoKeyVersions/1" } }, + "mainnet-band-oracle-connectors": { + "address": "e36ef556b8b5d955", + "key": { + "type": "google-kms", + "hashAlgorithm": "SHA2_256", + "resourceID": "projects/flow-foundation-admin/locations/global/keyRings/defi-actions/cryptoKeys/mainnet-defi-actions/cryptoKeyVersions/1" + } + }, "mainnet-flow-credit-market-deployer": { "address": "6b00ff876c299c61", "key": { diff --git a/local/setup_mainnet.sh b/local/setup_mainnet.sh index fb8eb26..3ebefca 100755 --- a/local/setup_mainnet.sh +++ b/local/setup_mainnet.sh @@ -31,6 +31,32 @@ flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-m --network mainnet \ --signer mainnet-flow-credit-market-deployer +# add WBTC to band oracle +cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "BTC" "A.dfc20aee650fcbdf.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault" --network testnet --signer mainnet-band-oracle-connectors && cd ../../.. + +# add WETH to band oracle +cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "ETH" "A.dfc20aee650fcbdf.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault" --network testnet --signer mainnet-band-oracle-connectors && cd ../../.. + +# add WBTC as supported token +flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_simple_interest_curve.cdc \ + 'A.dfc20aee650fcbdf.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault' \ + 0.8 \ + 1.0 \ + 1_000_000.0 \ + 1_000_000.0 \ + --network mainnet \ + --signer mainnet-flow-credit-market-deployer + +# add WETH as supported token +flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_simple_interest_curve.cdc \ + 'A.dfc20aee650fcbdf.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault' \ + 0.8 \ + 1.0 \ + 1_000_000.0 \ + 1_000_000.0 \ + --network mainnet \ + --signer mainnet-flow-credit-market-deployer + # TODO # swap # echo "swap Flow to MOET" @@ -60,7 +86,7 @@ flow transactions send ./lib/FlowCreditMarket/FlowActions/cadence/transactions/f # Setup UniV3 path tauUSDFv -> USDF -> WFLOW -flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ +flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ 'A.1654653399040a61.FlowToken.Vault' \ "0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c" \ '["0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c","0x2aaBea2058b5aC2D339b163C6Ab6f2b6d53aabED","0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e"]' \ @@ -70,7 +96,7 @@ flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_mu # Setup UniV3 path tauUSDFv -> USDF -> WBTC -flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ +flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ 'A.0x1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault' \ "0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c" \ '["0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c","0x2aaBea2058b5aC2D339b163C6Ab6f2b6d53aabED","0x717DAE2BaF7656BE9a9B01deE31d571a9d4c9579"]' \ @@ -79,7 +105,7 @@ flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_mu --signer mainnet-admin # Setup UniV3 path tauUSDFv -> USDF -> WETH -flow transactions send ../cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ +flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ 'A.0x1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault' \ "0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c" \ '["0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c","0x2aaBea2058b5aC2D339b163C6Ab6f2b6d53aabED","0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590"]' \ From c9691a22532c5383f312f716120753bc596ef6ac Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:24:39 -0500 Subject: [PATCH 18/31] tweak nil checks --- cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index 24de3dc..6642b14 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -407,11 +407,11 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { strategy: Type, collateral: Type ): Bool { - let composerConfig = self.configs[composer]! + let composerConfig = self.configs[composer] ?? nil if composerConfig == nil { return false } - let strategyConfig = composerConfig[strategy]! + let strategyConfig = composerConfig![strategy] ?? nil if strategyConfig == nil { return false } From b9ba3be22270c4c785b772f4b101ff4d4e5c3e55 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:31:06 -0500 Subject: [PATCH 19/31] bump From d1813fa2e210ee5a403ec75d1c531cd6a6999a63 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:27:50 -0500 Subject: [PATCH 20/31] remove comments --- .../flow-yield-vaults/admin/upsert_musdf_config.cdc | 3 --- 1 file changed, 3 deletions(-) diff --git a/cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc b/cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc index 74ff5c1..80f2244 100644 --- a/cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc +++ b/cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc @@ -40,9 +40,6 @@ transaction( let composerType = Type<@FlowYieldVaultsStrategiesV1_1.mUSDFStrategyComposer>() let strategyType = Type<@FlowYieldVaultsStrategiesV1_1.mUSDFStrategy>() - //issuer.purgeConfig() - - // === FLOW collateral === if swapPath.length > 0 { issuer.addOrUpdateCollateralConfig( composer: composerType, From defcb607e40d361bba5c8567424bd3efcd763825 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:14:20 -0500 Subject: [PATCH 21/31] address PR comments --- .../FlowYieldVaultsStrategiesV1_1.cdc | 458 ++++++++++++------ 1 file changed, 300 insertions(+), 158 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index 6642b14..4d4f32c 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -164,14 +164,19 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { return supported } + access(self) view fun _supportsCollateral(forStrategy: Type, collateral: Type): Bool { + if let strategyConfig = self.config[forStrategy] { + return strategyConfig[collateral] != nil + } + return false + } + /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the /// provided Vault type access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} { - let supportedInitVaults = self.getSupportedInitializationVaults(forStrategy: forStrategy) - if supportedInitVaults[initializedWith] == true { - return { initializedWith: true } - } - return {} + return self._supportsCollateral(forStrategy: forStrategy, collateral: initializedWith) + ? { initializedWith: true } + : {} } /// Composes a Strategy of the given type with the provided funds @@ -181,183 +186,320 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { withFunds: @{FungibleToken.Vault} ): @{FlowYieldVaults.Strategy} { let collateralType = withFunds.getType() - let strategyConfig = self.config[type] - ?? panic("Could not find a config for Strategy \(type.identifier) initialized with \(collateralType.identifier)") - let collateralConfig = strategyConfig[collateralType] - ?? panic("Could not find config for collateral \(collateralType.identifier) when creating Strategy \(type.identifier)") - // assign token types & associated EVM Addresses - let moetTokenType: Type = Type<@MOET.Vault>() - let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType) - ?? panic("Token Vault type \(moetTokenType.identifier) has not yet been registered with the VMbridge") - let yieldTokenEVMAddress = collateralConfig.yieldTokenEVMAddress - let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress) - ?? panic("Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())") + let collateralConfig = self._getCollateralConfig( + strategyType: type, + collateralType: collateralType + ) - // assign underlying asset EVM address & type - assumed to be stablecoin for the strategy - let underlying4626AssetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress( - vault: yieldTokenEVMAddress - ) ?? panic("Could not get the underlying asset's EVM address for ERC4626Vault \(yieldTokenEVMAddress.toString())") - let underlying4626AssetType = FlowEVMBridgeConfig.getTypeAssociated(with: underlying4626AssetEVMAddress) - ?? panic("Could not retrieve the VM Bridge associated Type for the ERC4626 underlying asset \(underlying4626AssetEVMAddress.toString())") + let tokens = self._resolveTokenBundle(collateralConfig: collateralConfig) - // create the oracle for the assets to be held in the AutoBalancer retrieving the NAV of the 4626 vault - let yieldTokenOracle = ERC4626PriceOracles.PriceOracle( - vault: yieldTokenEVMAddress, - asset: underlying4626AssetType, - // asset: moetTokenType, // TODO: make a composite oracle that returns the price denominated in MOET - uniqueID: uniqueID - ) + // Oracle used by AutoBalancer (tracks NAV of ERC4626 vault) + let yieldTokenOracle = self._createYieldTokenOracle( + yieldTokenEVMAddress: tokens.yieldTokenEVMAddress, + underlyingAssetType: tokens.underlying4626AssetType, + uniqueID: uniqueID + ) // Create recurring config for automatic rebalancing let recurringConfig = FlowYieldVaultsStrategiesV1_1._createRecurringConfig(withID: uniqueID) - // configure and AutoBalancer for this stack with native recurring scheduling - let autoBalancer = FlowYieldVaultsAutoBalancers._initNewAutoBalancer( - oracle: yieldTokenOracle, // used to determine value of deposits & when to rebalance - vaultType: yieldTokenType, // the type of Vault held by the AutoBalancer - lowerThreshold: 0.95, // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits - upperThreshold: 1.05, // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits - rebalanceSink: nil, // nil on init - will be set once a PositionSink is available - rebalanceSource: nil, // nil on init - not set for Strategy - recurringConfig: recurringConfig, // enables native AutoBalancer self-scheduling - uniqueID: uniqueID // identifies AutoBalancer as part of this Strategy - ) - // enables deposits of YieldToken to the AutoBalancer - let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") - // enables withdrawals of YieldToken from the AutoBalancer - let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") + // Create/store/publish/register AutoBalancer (returns authorized ref) + let balancerIO = self._initAutoBalancerAndIO( + oracle: yieldTokenOracle, + yieldTokenType: tokens.yieldTokenType, + recurringConfig: recurringConfig, + uniqueID: uniqueID + ) - // create MOET <-> YIELD swappers - // - // MOET -> YIELD - MOET can swap to YieldToken via two primary routes - // - via AMM swap pairing MOET <-> YIELD - // - via 4626 vault, swapping first to underlying asset then depositing to the 4626 vault - // MOET -> YIELD high-level Swapper then contains - // - MultiSwapper aggregates across two sub-swappers - // - MOET -> YIELD (UniV3 Swapper) - // - SequentialSwapper - // - MOET -> UNDERLYING (UniV3 Swapper) - // - UNDERLYING -> YIELD (ERC4626Swapper) - let moetToYieldAMMSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: FlowYieldVaultsStrategiesV1_1.univ3FactoryEVMAddress, - routerAddress: FlowYieldVaultsStrategiesV1_1.univ3RouterEVMAddress, - quoterAddress: FlowYieldVaultsStrategiesV1_1.univ3QuoterEVMAddress, - tokenPath: [moetTokenEVMAddress, yieldTokenEVMAddress], - feePath: [100], - inVault: moetTokenType, - outVault: yieldTokenType, - coaCapability: FlowYieldVaultsStrategiesV1_1._getCOACapability(), - uniqueID: uniqueID + // Swappers: MOET <-> YIELD (YIELD is ERC4626 vault token) + let moetToYieldSwapper = self._createMoetToYieldSwapper(tokens: tokens, uniqueID: uniqueID) + + let yieldToMoetSwapper = self._createUniV3Swapper( + tokenPath: [tokens.yieldTokenEVMAddress, tokens.moetTokenEVMAddress], + feePath: [100], + inVault: tokens.yieldTokenType, + outVault: tokens.moetTokenType, + uniqueID: uniqueID + ) + + // AutoBalancer-directed swap IO + let abaSwapSink = SwapConnectors.SwapSink( + swapper: moetToYieldSwapper, + sink: balancerIO.sink, + uniqueID: uniqueID + ) + let abaSwapSource = SwapConnectors.SwapSource( + swapper: yieldToMoetSwapper, + source: balancerIO.source, + uniqueID: uniqueID + ) + + // Open FlowCreditMarket position + let position = self._openCreditPosition( + funds: <-withFunds, + issuanceSink: abaSwapSink, + repaymentSource: abaSwapSource + ) + + // Position Sink/Source (only Sink needed here, Source stays inside Strategy impl) + let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true) + + // Yield -> Collateral swapper for recollateralization + let yieldToCollateralSwapper = self._createYieldToCollateralSwapper( + collateralConfig: collateralConfig, + yieldTokenEVMAddress: tokens.yieldTokenEVMAddress, + yieldTokenType: tokens.yieldTokenType, + collateralType: collateralType, + uniqueID: uniqueID + ) + + let positionSwapSink = SwapConnectors.SwapSink( + swapper: yieldToCollateralSwapper, + sink: positionSink, + uniqueID: uniqueID + ) + + // Set AutoBalancer sink for overflow -> recollateralize + balancerIO.autoBalancer.setSink(positionSwapSink, updateSinkID: true) + + return <-create FlowYieldVaultsStrategiesV1_1.mUSDFStrategy( + id: uniqueID, + collateralType: collateralType, + position: position + ) + } + + /* =========================== + Helpers + =========================== */ + + access(self) fun _getCollateralConfig( + strategyType: Type, + collateralType: Type + ): FlowYieldVaultsStrategiesV1_1.CollateralConfig { + let strategyConfig = self.config[strategyType] + ?? panic( + "Could not find a config for Strategy \(strategyType.identifier) initialized with \(collateralType.identifier)" ) - // Swap MOET -> UNDERLYING via AMM - let moetToUnderlyingAssetSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: FlowYieldVaultsStrategiesV1_1.univ3FactoryEVMAddress, - routerAddress: FlowYieldVaultsStrategiesV1_1.univ3RouterEVMAddress, - quoterAddress: FlowYieldVaultsStrategiesV1_1.univ3QuoterEVMAddress, - tokenPath: [moetTokenEVMAddress, underlying4626AssetEVMAddress], - feePath: [100], - inVault: moetTokenType, - outVault: underlying4626AssetType, - coaCapability: FlowYieldVaultsStrategiesV1_1._getCOACapability(), - uniqueID: uniqueID + + return strategyConfig[collateralType] + ?? panic( + "Could not find config for collateral \(collateralType.identifier) when creating Strategy \(strategyType.identifier)" ) - // Swap UNDERLYING -> YIELD via ERC4626 Vault - let underlyingTo4626Swapper = ERC4626SwapConnectors.Swapper( - asset: underlying4626AssetType, - vault: yieldTokenEVMAddress, - coa: FlowYieldVaultsStrategiesV1_1._getCOACapability(), - feeSource: FlowYieldVaultsStrategiesV1_1._createFeeSource(withID: uniqueID), - uniqueID: uniqueID + } + + access(self) struct TokenBundle { + access(self) let moetTokenType: Type + access(self) let moetTokenEVMAddress: EVM.EVMAddress + + access(self) let yieldTokenType: Type + access(self) let yieldTokenEVMAddress: EVM.EVMAddress + + access(self) let underlying4626AssetType: Type + access(self) let underlying4626AssetEVMAddress: EVM.EVMAddress + } + + access(self) fun _resolveTokenBundle( + collateralConfig: FlowYieldVaultsStrategiesV1_1.CollateralConfig + ): TokenBundle { + // MOET + let moetTokenType = Type<@MOET.Vault>() + let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType) + ?? panic("Token Vault type \(moetTokenType.identifier) has not yet been registered with the VMbridge") + + // YIELD (ERC4626 vault token) + let yieldTokenEVMAddress = collateralConfig.yieldTokenEVMAddress + let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress) + ?? panic( + "Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())" ) - // Compose v3 swapper & 4626 swapper into sequential swapper for MOET -> UNDERLYING -> YIELD - let moetToYieldSeqSwapper = SwapConnectors.SequentialSwapper( - swappers: [moetToUnderlyingAssetSwapper, underlyingTo4626Swapper], - uniqueID: uniqueID + + // UNDERLYING asset of the ERC4626 vault + let underlying4626AssetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress(vault: yieldTokenEVMAddress) + ?? panic( + "Could not get the underlying asset's EVM address for ERC4626Vault \(yieldTokenEVMAddress.toString())" ) - // Finally, add the two MOET -> YIELD swappers into an aggregate MultiSwapper - let moetToYieldSwapper = SwapConnectors.MultiSwapper( - inVault: moetTokenType, - outVault: yieldTokenType, - swappers: [moetToYieldAMMSwapper, moetToYieldSeqSwapper], - uniqueID: uniqueID + let underlying4626AssetType = FlowEVMBridgeConfig.getTypeAssociated(with: underlying4626AssetEVMAddress) + ?? panic( + "Could not retrieve the VM Bridge associated Type for the ERC4626 underlying asset \(underlying4626AssetEVMAddress.toString())" ) - // YIELD -> MOET - // - Targets the MOET <-> YIELD pool as the only route since withdraws from the ERC4626 Vault are async - let yieldToMOETSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: FlowYieldVaultsStrategiesV1_1.univ3FactoryEVMAddress, - routerAddress: FlowYieldVaultsStrategiesV1_1.univ3RouterEVMAddress, - quoterAddress: FlowYieldVaultsStrategiesV1_1.univ3QuoterEVMAddress, - tokenPath: [yieldTokenEVMAddress, moetTokenEVMAddress], - feePath: [100], - inVault: yieldTokenType, - outVault: moetTokenType, - coaCapability: FlowYieldVaultsStrategiesV1_1._getCOACapability(), + return TokenBundle( + moetTokenType: moetTokenType, + moetTokenEVMAddress: moetTokenEVMAddress, + yieldTokenType: yieldTokenType, + yieldTokenEVMAddress: yieldTokenEVMAddress, + underlying4626AssetType: underlying4626AssetType, + underlying4626AssetEVMAddress: underlying4626AssetEVMAddress + ) + } + + access(self) fun _createYieldTokenOracle( + yieldTokenEVMAddress: EVM.EVMAddress, + underlyingAssetType: Type, + uniqueID: DeFiActions.UniqueIdentifier + ): ERC4626PriceOracles.PriceOracle { + return ERC4626PriceOracles.PriceOracle( + vault: yieldTokenEVMAddress, + asset: underlyingAssetType, + uniqueID: uniqueID + ) + } + + access(self) fun _createUniV3Swapper( + tokenPath: [EVM.EVMAddress], + feePath: [UInt32], + inVault: Type, + outVault: Type, + uniqueID: DeFiActions.UniqueIdentifier + ): UniswapV3SwapConnectors.Swapper { + return UniswapV3SwapConnectors.Swapper( + factoryAddress: FlowYieldVaultsStrategiesV1_1.univ3FactoryEVMAddress, + routerAddress: FlowYieldVaultsStrategiesV1_1.univ3RouterEVMAddress, + quoterAddress: FlowYieldVaultsStrategiesV1_1.univ3QuoterEVMAddress, + tokenPath: tokenPath, + feePath: feePath, + inVault: inVault, + outVault: outVault, + coaCapability: FlowYieldVaultsStrategiesV1_1._getCOACapability(), + uniqueID: uniqueID + ) + } + + access(self) fun _createMoetToYieldSwapper( + tokens: TokenBundle, + uniqueID: DeFiActions.UniqueIdentifier + ): SwapConnectors.MultiSwapper { + // Direct MOET -> YIELD via AMM + let moetToYieldAMM = self._createUniV3Swapper( + tokenPath: [tokens.moetTokenEVMAddress, tokens.yieldTokenEVMAddress], + feePath: [100], + inVault: tokens.moetTokenType, + outVault: tokens.yieldTokenType, + uniqueID: uniqueID + ) + + // MOET -> UNDERLYING via AMM + let moetToUnderlying = self._createUniV3Swapper( + tokenPath: [tokens.moetTokenEVMAddress, tokens.underlying4626AssetEVMAddress], + feePath: [100], + inVault: tokens.moetTokenType, + outVault: tokens.underlying4626AssetType, + uniqueID: uniqueID + ) + + // UNDERLYING -> YIELD via ERC4626 vault + let underlyingTo4626 = ERC4626SwapConnectors.Swapper( + asset: tokens.underlying4626AssetType, + vault: tokens.yieldTokenEVMAddress, + coa: FlowYieldVaultsStrategiesV1_1._getCOACapability(), + feeSource: FlowYieldVaultsStrategiesV1_1._createFeeSource(withID: uniqueID), + uniqueID: uniqueID + ) + + let seq = SwapConnectors.SequentialSwapper( + swappers: [moetToUnderlying, underlyingTo4626], + uniqueID: uniqueID + ) + + return SwapConnectors.MultiSwapper( + inVault: tokens.moetTokenType, + outVault: tokens.yieldTokenType, + swappers: [moetToYieldAMM, seq], + uniqueID: uniqueID + ) + } + + /// Returned bundle for stored AutoBalancer interactions (reference + caps) + access(self) struct AutoBalancerIO { + access(self) let autoBalancer: + auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, DeFiActions.Schedule, FungibleToken.Withdraw) + &DeFiActions.AutoBalancer + + access(self) let sink: Capability<&{DeFiActions.Sink}> + access(self) let source: Capability<&{DeFiActions.Source}> + } + + access(self) fun _initAutoBalancerAndIO( + oracle: {DeFiActions.PriceOracle}, + yieldTokenType: Type, + recurringConfig: DeFiActions.AutoBalancerRecurringConfig?, + uniqueID: DeFiActions.UniqueIdentifier + ): AutoBalancerIO { + // NOTE: This stores the AutoBalancer in FlowYieldVaultsAutoBalancers storage and returns an authorized ref. + let autoBalancerRef = + FlowYieldVaultsAutoBalancers._initNewAutoBalancer( + oracle: oracle, + vaultType: yieldTokenType, + lowerThreshold: 0.95, + upperThreshold: 1.05, + rebalanceSink: nil, + rebalanceSource: nil, + recurringConfig: recurringConfig, uniqueID: uniqueID ) - // init SwapSink directing swapped funds to AutoBalancer - // - // Swaps provided MOET to YIELD & deposits to the AutoBalancer - let abaSwapSink = SwapConnectors.SwapSink(swapper: moetToYieldSwapper, sink: abaSink, uniqueID: uniqueID) - // Swaps YIELD & provides swapped MOET, sourcing YIELD from the AutoBalancer - let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToMOETSwapper, source: abaSource, uniqueID: uniqueID) - - // open a FlowCreditMarket position - let poolCap = FlowYieldVaultsStrategiesV1_1.account.storage.copy>( - from: FlowCreditMarket.PoolCapStoragePath - ) ?? panic("Missing or invalid pool capability") + let sink = autoBalancerRef.createBalancerSink() + ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") + let source = autoBalancerRef.createBalancerSource() + ?? panic("Could not retrieve Source from AutoBalancer with id \(uniqueID.id)") + + return AutoBalancerIO( + autoBalancer: autoBalancerRef, + sink: sink, + source: source + ) + } + + access(self) fun _openCreditPosition( + funds: @{FungibleToken.Vault}, + issuanceSink: Capability<&{DeFiActions.Sink}>, + repaymentSource: Capability<&{DeFiActions.Source}> + ): FlowCreditMarket.Position { + let poolCap = FlowYieldVaultsStrategiesV1_1.account.storage.copy< + Capability + >(from: FlowCreditMarket.PoolCapStoragePath) + ?? panic("Missing or invalid pool capability") + let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap") let pid = poolRef.createPosition( - funds: <-withFunds, - issuanceSink: abaSwapSink, - repaymentSource: abaSwapSource, - pushToDrawDownSink: true - ) - let position = FlowCreditMarket.Position(id: pid, pool: poolCap) + funds: <-funds, + issuanceSink: issuanceSink, + repaymentSource: repaymentSource, + pushToDrawDownSink: true + ) - // get Sink & Source connectors relating to the new Position - let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true) - let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) + return FlowCreditMarket.Position(id: pid, pool: poolCap) + } - // init YieldToken -> Collateral Swapper + access(self) fun _createYieldToCollateralSwapper( + collateralConfig: FlowYieldVaultsStrategiesV1_1.CollateralConfig, + yieldTokenEVMAddress: EVM.EVMAddress, + yieldTokenType: Type, + collateralType: Type, + uniqueID: DeFiActions.UniqueIdentifier + ): UniswapV3SwapConnectors.Swapper { + // CollateralConfig.init already validates: + // - path length > 1 + // - fee length == path length - 1 + // - path[0] == yield token // - // get UniswapV3 path configs - var uniV3AddressPath: [EVM.EVMAddress] = collateralConfig.yieldToCollateralUniV3AddressPath - assert(uniV3AddressPath.length > 1, message: "Invalid Uniswap V3 swap path length of \(uniV3AddressPath.length)") - assert(uniV3AddressPath[0].equals(yieldTokenEVMAddress), - message: "UniswapV3 swap path does not match - expected path[0] to be \(yieldTokenEVMAddress.toString()) but found \(uniV3AddressPath[0].toString())") - - var uniV3FeePath = collateralConfig.yieldToCollateralUniV3FeePath - assert(uniV3FeePath.length > 0, message: "Invalid Uniswap V3 fee path length of \(uniV3FeePath.length)") - - // initialize the swapper used for recollateralization of the lending position as YIELD increases in value - let yieldToCollateralSwapper = UniswapV3SwapConnectors.Swapper( - factoryAddress: FlowYieldVaultsStrategiesV1_1.univ3FactoryEVMAddress, - routerAddress: FlowYieldVaultsStrategiesV1_1.univ3RouterEVMAddress, - quoterAddress: FlowYieldVaultsStrategiesV1_1.univ3QuoterEVMAddress, - tokenPath: uniV3AddressPath, - feePath: uniV3FeePath, - inVault: yieldTokenType, - outVault: collateralType, - coaCapability: FlowYieldVaultsStrategiesV1_1._getCOACapability(), - uniqueID: uniqueID - ) - // allows for YIELD to be deposited to the Position as the collateral basis - let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToCollateralSwapper, sink: positionSink, uniqueID: uniqueID) - - // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value, recollateralizing - // the position - autoBalancer.setSink(positionSwapSink, updateSinkID: true) + // Keep a defensive check in case configs were migrated / constructed elsewhere. + let tokenPath = collateralConfig.yieldToCollateralUniV3AddressPath + assert( + tokenPath[0].equals(yieldTokenEVMAddress), + message: + "Config mismatch: expected yield token \(yieldTokenEVMAddress.toString()) but got \(tokenPath[0].toString())" + ) - // Use the same uniqueID passed to createStrategy so Strategy.burnCallback - // calls _cleanupAutoBalancer with the correct ID - return <-create mUSDFStrategy( - id: uniqueID, - collateralType: collateralType, - position: position + return self._createUniV3Swapper( + tokenPath: tokenPath, + feePath: collateralConfig.yieldToCollateralUniV3FeePath, + inVault: yieldTokenType, + outVault: collateralType, + uniqueID: uniqueID ) } } From 9d6de4f64b8939b6b7c222fa982afdbc03041fa7 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:29:41 -0500 Subject: [PATCH 22/31] fix test --- cadence/tests/test_helpers.cdc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 62ea0c5..1155e84 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -338,6 +338,8 @@ access(all) fun deployContracts() { "0x8dd92c8d0C3b304255fF9D98ae59c3385F88360C" ] ) + + Test.expect(err, Test.beNil()) // FLOW looping strategy err = Test.deployContract( name: "PMStrategiesV1", From a074e62a28c075559e6ea1d6ff1a9b037e091592 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:58:45 -0500 Subject: [PATCH 23/31] fix access --- .../FlowYieldVaultsStrategiesV1_1.cdc | 82 ++++++++++++------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index 4d4f32c..f539b03 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -135,6 +135,53 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { } } + access(all) struct TokenBundle { + access(all) let moetTokenType: Type + access(all) let moetTokenEVMAddress: EVM.EVMAddress + + access(all) let yieldTokenType: Type + access(all) let yieldTokenEVMAddress: EVM.EVMAddress + + access(all) let underlying4626AssetType: Type + access(all) let underlying4626AssetEVMAddress: EVM.EVMAddress + + init( + moetTokenType: Type, + moetTokenEVMAddress: EVM.EVMAddress, + yieldTokenType: Type, + yieldTokenEVMAddress: EVM.EVMAddress, + underlying4626AssetType: Type, + underlying4626AssetEVMAddress: EVM.EVMAddress + ) { + self.moetTokenType = moetTokenType + self.moetTokenEVMAddress = moetTokenEVMAddress + self.yieldTokenType = yieldTokenType + self.yieldTokenEVMAddress = yieldTokenEVMAddress + self.underlying4626AssetType = underlying4626AssetType + self.underlying4626AssetEVMAddress = underlying4626AssetEVMAddress + } + } + + /// Returned bundle for stored AutoBalancer interactions (reference + caps) + access(all) struct AutoBalancerIO { + access(all) let autoBalancer: + auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, DeFiActions.Schedule, FungibleToken.Withdraw) + &DeFiActions.AutoBalancer + + access(all) let sink: {DeFiActions.Sink} + access(all) let source: {DeFiActions.Source} + + init( + autoBalancer: auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, DeFiActions.Schedule, FungibleToken.Withdraw) &DeFiActions.AutoBalancer, + sink: {DeFiActions.Sink}, + source: {DeFiActions.Source} + ) { + self.sink = sink + self.source = source + self.autoBalancer = autoBalancer + } + } + /// This StrategyComposer builds a mUSDFStrategy access(all) resource mUSDFStrategyComposer : FlowYieldVaults.StrategyComposer { /// { Strategy Type: { Collateral Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig } } @@ -289,20 +336,9 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { ) } - access(self) struct TokenBundle { - access(self) let moetTokenType: Type - access(self) let moetTokenEVMAddress: EVM.EVMAddress - - access(self) let yieldTokenType: Type - access(self) let yieldTokenEVMAddress: EVM.EVMAddress - - access(self) let underlying4626AssetType: Type - access(self) let underlying4626AssetEVMAddress: EVM.EVMAddress - } - access(self) fun _resolveTokenBundle( collateralConfig: FlowYieldVaultsStrategiesV1_1.CollateralConfig - ): TokenBundle { + ): FlowYieldVaultsStrategiesV1_1.TokenBundle { // MOET let moetTokenType = Type<@MOET.Vault>() let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType) @@ -325,7 +361,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { "Could not retrieve the VM Bridge associated Type for the ERC4626 underlying asset \(underlying4626AssetEVMAddress.toString())" ) - return TokenBundle( + return FlowYieldVaultsStrategiesV1_1.TokenBundle( moetTokenType: moetTokenType, moetTokenEVMAddress: moetTokenEVMAddress, yieldTokenType: yieldTokenType, @@ -368,7 +404,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { } access(self) fun _createMoetToYieldSwapper( - tokens: TokenBundle, + tokens: FlowYieldVaultsStrategiesV1_1.TokenBundle, uniqueID: DeFiActions.UniqueIdentifier ): SwapConnectors.MultiSwapper { // Direct MOET -> YIELD via AMM @@ -411,22 +447,12 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { ) } - /// Returned bundle for stored AutoBalancer interactions (reference + caps) - access(self) struct AutoBalancerIO { - access(self) let autoBalancer: - auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, DeFiActions.Schedule, FungibleToken.Withdraw) - &DeFiActions.AutoBalancer - - access(self) let sink: Capability<&{DeFiActions.Sink}> - access(self) let source: Capability<&{DeFiActions.Source}> - } - access(self) fun _initAutoBalancerAndIO( oracle: {DeFiActions.PriceOracle}, yieldTokenType: Type, recurringConfig: DeFiActions.AutoBalancerRecurringConfig?, uniqueID: DeFiActions.UniqueIdentifier - ): AutoBalancerIO { + ): FlowYieldVaultsStrategiesV1_1.AutoBalancerIO { // NOTE: This stores the AutoBalancer in FlowYieldVaultsAutoBalancers storage and returns an authorized ref. let autoBalancerRef = FlowYieldVaultsAutoBalancers._initNewAutoBalancer( @@ -445,7 +471,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { let source = autoBalancerRef.createBalancerSource() ?? panic("Could not retrieve Source from AutoBalancer with id \(uniqueID.id)") - return AutoBalancerIO( + return FlowYieldVaultsStrategiesV1_1.AutoBalancerIO( autoBalancer: autoBalancerRef, sink: sink, source: source @@ -454,8 +480,8 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { access(self) fun _openCreditPosition( funds: @{FungibleToken.Vault}, - issuanceSink: Capability<&{DeFiActions.Sink}>, - repaymentSource: Capability<&{DeFiActions.Source}> + issuanceSink: {DeFiActions.Sink}, + repaymentSource: {DeFiActions.Source} ): FlowCreditMarket.Position { let poolCap = FlowYieldVaultsStrategiesV1_1.account.storage.copy< Capability From ac8cf2bccf3c4933c73f5b0c2d085697ef375da1 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Thu, 8 Jan 2026 12:35:38 -0500 Subject: [PATCH 24/31] address PR comments --- cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc | 10 ---------- local/setup_emulator.sh | 4 ++-- local/setup_mainnet.sh | 10 +++++----- local/setup_testnet.sh | 2 +- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index f539b03..3a196b9 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -13,18 +13,14 @@ import "ERC4626Utils" // Lending protocol import "FlowCreditMarket" // FlowYieldVaults platform -import "FlowYieldVaultsClosedBeta" import "FlowYieldVaults" import "FlowYieldVaultsAutoBalancers" // scheduler import "FlowTransactionScheduler" -import "FlowYieldVaultsSchedulerRegistry" // tokens import "MOET" // vm bridge import "FlowEVMBridgeConfig" -import "FlowEVMBridgeUtils" -import "FlowEVMBridge" // live oracles import "ERC4626PriceOracles" @@ -530,12 +526,6 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { } } - access(all) fun createIssuer( - configs: {Type: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}} - ): @StrategyComposerIssuer { - return <- create StrategyComposerIssuer(configs: configs) - } - access(all) entitlement Configure access(self) diff --git a/local/setup_emulator.sh b/local/setup_emulator.sh index 4a44d2a..c726fb5 100755 --- a/local/setup_emulator.sh +++ b/local/setup_emulator.sh @@ -51,8 +51,8 @@ flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strate flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strategy_composer.cdc \ - 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1_1.USDFStrategy' \ - 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1_1.USDFStrategyComposer' \ + 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy' \ + 'A.045a1763c93006ca.FlowYieldVaultsStrategiesV1_1.mUSDFStrategyComposer' \ /storage/FlowYieldVaultsStrategyV1_1ComposerIssuer_0x045a1763c93006ca \ --signer emulator-flow-yield-vaults diff --git a/local/setup_mainnet.sh b/local/setup_mainnet.sh index 3ebefca..e150eda 100755 --- a/local/setup_mainnet.sh +++ b/local/setup_mainnet.sh @@ -32,10 +32,10 @@ flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-m --signer mainnet-flow-credit-market-deployer # add WBTC to band oracle -cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "BTC" "A.dfc20aee650fcbdf.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault" --network testnet --signer mainnet-band-oracle-connectors && cd ../../.. +cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "BTC" "A.dfc20aee650fcbdf.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault" --network mainnet --signer mainnet-band-oracle-connectors && cd ../../.. # add WETH to band oracle -cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "ETH" "A.dfc20aee650fcbdf.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault" --network testnet --signer mainnet-band-oracle-connectors && cd ../../.. +cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "ETH" "A.dfc20aee650fcbdf.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault" --network mainnet --signer mainnet-band-oracle-connectors && cd ../../.. # add WBTC as supported token flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_simple_interest_curve.cdc \ @@ -97,7 +97,7 @@ flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_mus # Setup UniV3 path tauUSDFv -> USDF -> WBTC flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ - 'A.0x1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault' \ + 'A.1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault' \ "0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c" \ '["0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c","0x2aaBea2058b5aC2D339b163C6Ab6f2b6d53aabED","0x717DAE2BaF7656BE9a9B01deE31d571a9d4c9579"]' \ '[100,3000]' \ @@ -106,7 +106,7 @@ flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_mus # Setup UniV3 path tauUSDFv -> USDF -> WETH flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_musdf_config.cdc \ - 'A.0x1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault' \ + 'A.1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault' \ "0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c" \ '["0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c","0x2aaBea2058b5aC2D339b163C6Ab6f2b6d53aabED","0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590"]' \ '[100,3000]' \ @@ -114,7 +114,7 @@ flow transactions send ./cadence/transactions/flow-yield-vaults/admin/upsert_mus --signer mainnet-admin # -# add TracerStrategy as supported Strategy with the ability to initialize when new YieldVaults are created +# add mUSDFStrategy as supported Strategy with the ability to initialize when new YieldVaults are created flow transactions send ./cadence/transactions/flow-yield-vaults/admin/add_strategy_composer.cdc \ 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy' \ 'A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1_1.mUSDFStrategyComposer' \ diff --git a/local/setup_testnet.sh b/local/setup_testnet.sh index 4273234..1b57304 100755 --- a/local/setup_testnet.sh +++ b/local/setup_testnet.sh @@ -15,7 +15,7 @@ flow transactions send ./lib/flow-evm-bridge/cadence/transactions/bridge/onboard # configure FlowCreditMarket # # add MOET - USD association on Band Oracle -cd ./libs/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "USD" "A.426f0458ced60037.MOET.Vault" --network testnet --signer testnet-band-oracle-connectors +cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "USD" "A.426f0458ced60037.MOET.Vault" --network testnet --signer testnet-band-oracle-connectors # create Pool with MOET as default token with Mock Oracle flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-factory/create_and_store_pool.cdc 'A.426f0458ced60037.MOET.Vault' --network testnet --signer testnet-flow-credit-market-deployer From d3cfcb3d719c1a953ec4bee7ae8790b86be352a8 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:15:20 -0500 Subject: [PATCH 25/31] address comments --- .../FlowYieldVaultsStrategiesV1_1.cdc | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index 3a196b9..e5fee91 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -565,27 +565,22 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { strategy: Type, collateral: Type ): Bool { - let composerConfig = self.configs[composer] ?? nil - if composerConfig == nil { - return false - } - let strategyConfig = composerConfig![strategy] ?? nil - if strategyConfig == nil { - return false + if let composerConfig = self.configs[composer] { + if let strategyConfig = composerConfig[strategy] { + return strategyConfig[collateral] != nil + } } - return strategyConfig![collateral] != nil + return false } - access(all) view fun getSupportedComposers(): {Type: Bool} { - return { - Type<@mUSDFStrategyComposer>(): true - } + access(all) view fun isSupportedComposer(_ type: Type): Bool { + return type == Type() } access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} { pre { - self.getSupportedComposers()[type] == true: + self.isSupportedComposer(type) == true: "Unsupported StrategyComposer \(type.identifier) requested" - (&self.configs[type] as &{Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}?) != nil: + self.configs[type] != nil: "Could not find config for StrategyComposer \(type.identifier)" } switch type { @@ -602,7 +597,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { config: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}} ) { pre { - self.getSupportedComposers()[composer] == true: + self.isSupportedComposer(composer) == true: "Unsupported StrategyComposer Type \(composer.identifier)" } @@ -618,7 +613,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { // Merge instead of overwrite let existingComposerConfig = self.configs[composer] ?? {} - var mergedComposerConfig: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}} = existingComposerConfig + var mergedComposerConfig = existingComposerConfig for stratType in config.keys { let newPerCollateral = config[stratType]! @@ -643,7 +638,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { yieldToCollateralFeePath: [UInt32] ) { pre { - self.getSupportedComposers()[composer] == true: + self.isSupportedComposer(composer) == true: "Unsupported StrategyComposer Type \(composer.identifier)" strategyType.isSubtype(of: Type<@{FlowYieldVaults.Strategy}>()): "Strategy type \(strategyType.identifier) is not a FlowYieldVaults.Strategy" @@ -659,7 +654,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { ) // Wrap into the nested config expected by upsertConfigFor - let singleCollateralConfig: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}} = { + let singleCollateralConfig = { strategyType: { collateralVaultType: base } @@ -670,9 +665,9 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { access(Configure) fun purgeConfig() { self.configs = { Type<@mUSDFStrategyComposer>(): { - Type<@mUSDFStrategy>(): {} + Type<@mUSDFStrategy>(): {} as {type: FlowYieldVaultsStrategiesV1_1.CollateralConfig} } - } as {Type: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}} + } } } @@ -750,8 +745,9 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { self.config = {} let moetType = Type<@MOET.Vault>() - let moetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>()) - ?? panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge") + if FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>()) == nil { + panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge") + } let configs: {Type: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}} = { Type<@mUSDFStrategyComposer>(): { From 6f00c681427dfd0d36d35c68d8307c3b0304dc34 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:55:44 -0500 Subject: [PATCH 26/31] fix typo --- cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index e5fee91..fad2912 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -574,7 +574,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { } access(all) view fun isSupportedComposer(_ type: Type): Bool { - return type == Type() + return type == Type<@mUSDFStrategyComposer>() } access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} { pre { From 88f3e5e190a92c8370676aa7378c437a4f049114 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:26:22 -0500 Subject: [PATCH 27/31] typos --- cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index fad2912..0285cc1 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -665,7 +665,7 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { access(Configure) fun purgeConfig() { self.configs = { Type<@mUSDFStrategyComposer>(): { - Type<@mUSDFStrategy>(): {} as {type: FlowYieldVaultsStrategiesV1_1.CollateralConfig} + Type<@mUSDFStrategy>(): {} as {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig} } } } @@ -749,9 +749,9 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge") } - let configs: {Type: {Type: {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}}} = { + let configs = { Type<@mUSDFStrategyComposer>(): { - Type<@mUSDFStrategy>(): {} + Type<@mUSDFStrategy>(): ({} as {Type: FlowYieldVaultsStrategiesV1_1.CollateralConfig}) } } self.account.storage.save(<-create StrategyComposerIssuer(configs: configs), to: self.IssuerStoragePath) From 05d179bbf3ffed9c686375733d3a217f0257cb91 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:41:21 -0500 Subject: [PATCH 28/31] restore getSupportedComposers method --- cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc index 0285cc1..7cfe055 100644 --- a/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc +++ b/cadence/contracts/FlowYieldVaultsStrategiesV1_1.cdc @@ -573,7 +573,13 @@ access(all) contract FlowYieldVaultsStrategiesV1_1 { return false } - access(all) view fun isSupportedComposer(_ type: Type): Bool { + access(all) view fun getSupportedComposers(): {Type: Bool} { + return { + Type<@mUSDFStrategyComposer>(): true + } + } + + access(self) view fun isSupportedComposer(_ type: Type): Bool { return type == Type<@mUSDFStrategyComposer>() } access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} { From e98494f55f5202eae70e70fda26327e76bf9c23c Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:11:12 -0500 Subject: [PATCH 29/31] pre deployment fixes --- cadence/contracts/FlowYieldVaults.cdc | 2 +- flow.json | 18 +++++++-------- lib/FlowCreditMarket | 2 +- local/setup_mainnet.sh | 32 +++++++++++++++++---------- local/setup_testnet.sh | 8 +++---- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/cadence/contracts/FlowYieldVaults.cdc b/cadence/contracts/FlowYieldVaults.cdc index cb20b19..270bec7 100644 --- a/cadence/contracts/FlowYieldVaults.cdc +++ b/cadence/contracts/FlowYieldVaults.cdc @@ -467,4 +467,4 @@ access(all) contract FlowYieldVaults { let cap = self.account.capabilities.storage.issue<&StrategyFactory>(self.FactoryStoragePath) self.account.capabilities.publish(cap, at: self.FactoryPublicPath) } -} \ No newline at end of file +} diff --git a/flow.json b/flow.json index ca125d7..0914fc3 100644 --- a/flow.json +++ b/flow.json @@ -314,7 +314,7 @@ }, "EVM": { "source": "mainnet://e467b9dd11fa00df.EVM", - "hash": "2a4782c7459dc5b72c034f67c8dd1beac6bb9b29104772a3e6eb6850718bb3b4", + "hash": "c77a07a7eac28b1470b148204d6f2e3527d931b2d2df341618ab888201316a0b", "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "e467b9dd11fa00df", @@ -479,7 +479,7 @@ }, "FlowTransactionSchedulerUtils": { "source": "mainnet://e467b9dd11fa00df.FlowTransactionSchedulerUtils", - "hash": "b5d6f06dd43e4cee907e08a5bc46df0bb9c2338d806d9d253789aee4c4ac01ad", + "hash": "429ed886472cd65def9e5ab1dd20079b0dcfb23095d18d54077767ac3316a8ce", "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "e467b9dd11fa00df", @@ -885,11 +885,11 @@ }, { "value": "0x8dd92c8d0C3b304255fF9D98ae59c3385F88360C", - "type": "String" - } - ] - }, - { + "type": "String" + } + ] + }, + { "name": "PMStrategiesV1", "args": [ { @@ -1017,7 +1017,7 @@ } ] }, - { + { "name": "PMStrategiesV1", "args": [ { @@ -1122,7 +1122,7 @@ } ] }, - { + { "name": "PMStrategiesV1", "args": [ { diff --git a/lib/FlowCreditMarket b/lib/FlowCreditMarket index 5da5a4f..32dbf84 160000 --- a/lib/FlowCreditMarket +++ b/lib/FlowCreditMarket @@ -1 +1 @@ -Subproject commit 5da5a4f4f6e2a45130566b79da9d2c9f3a955937 +Subproject commit 32dbf8483a8d875625a5a9d285aeccc95518f946 diff --git a/local/setup_mainnet.sh b/local/setup_mainnet.sh index 9906066..e34e80c 100755 --- a/local/setup_mainnet.sh +++ b/local/setup_mainnet.sh @@ -32,26 +32,34 @@ flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-m --signer mainnet-flow-credit-market-deployer # add WBTC to band oracle -cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "BTC" "A.dfc20aee650fcbdf.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault" --network mainnet --signer mainnet-band-oracle-connectors && cd ../../.. +cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "BTC" "A.1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault" --network mainnet --signer mainnet-band-oracle-connectors && cd ../../.. # add WETH to band oracle -cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "ETH" "A.dfc20aee650fcbdf.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault" --network mainnet --signer mainnet-band-oracle-connectors && cd ../../.. +cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "ETH" "A.1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault" --network mainnet --signer mainnet-band-oracle-connectors && cd ../../.. # add WBTC as supported token -flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_simple_interest_curve.cdc \ - 'A.dfc20aee650fcbdf.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault' \ +flow transactions send ../lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_kink_curve.cdc \ + 'A.1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault' \ 0.8 \ 1.0 \ + 0.8 \ + 0.0 \ + 0.04 \ + 0.4 \ 1_000_000.0 \ 1_000_000.0 \ --network mainnet \ --signer mainnet-flow-credit-market-deployer # add WETH as supported token -flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_simple_interest_curve.cdc \ - 'A.dfc20aee650fcbdf.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault' \ +flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_kink_curve.cdc \ + 'A.1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault' \ 0.8 \ 1.0 \ + 0.8 \ + 0.0 \ + 0.04 \ + 0.4 \ 1_000_000.0 \ 1_000_000.0 \ --network mainnet \ @@ -185,9 +193,9 @@ flow transactions send ./lib/FlowCreditMarket/cadence/tests/transactions/flow-cr # sanity test # flow transactions send ./cadence/transactions/flow-yield-vaults/admin/grant_beta.cdc \ # --authorizer mainnet-admin, \ -# --proposer \ # --payer mainnet-admin \ -# --network mainnet +# --network mainnet \ +# --proposer # test FlowYieldVault strategy @@ -195,9 +203,9 @@ flow transactions send ./lib/FlowCreditMarket/cadence/tests/transactions/flow-cr # A.b1d63873c3cc9f79.FlowYieldVaultsStrategies.mUSDCStrategy \ # A.1654653399040a61.FlowToken.Vault \ # 1.0 \ -# --signer \ # --compute-limit 9999 \ -# --network mainnet +# --network mainnet \ +# --signer # # test PEAK MONEY strategy @@ -207,9 +215,9 @@ flow transactions send ./lib/FlowCreditMarket/cadence/tests/transactions/flow-cr # A.b1d63873c3cc9f79.PMStrategiesV1.syWFLOWvStrategy \ # A.1654653399040a61.FlowToken.Vault \ # 1.0 \ -# --signer \ # --compute-limit 9999 \ -# --network mainnet +# --network mainnet \ +# --signer # # PYUSD0 # flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ diff --git a/local/setup_testnet.sh b/local/setup_testnet.sh index f8ee466..c8cfb0f 100755 --- a/local/setup_testnet.sh +++ b/local/setup_testnet.sh @@ -171,18 +171,18 @@ flow transactions send ./lib/flow-evm-bridge/cadence/transactions/flow-token/tra # A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy \ # A.dfc20aee650fcbdf.EVMVMBridgedToken_208d09d2a6dd176e3e95b3f0de172a7471c5b2d6.Vault \ # 0.00001 \ -# --signer \ # --compute-limit 9999 \ -# --network testnet +# --network testnet \ +# --signer # # WETH (ETHf) # flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ # A.d2580caf2ef07c2f.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy \ # A.dfc20aee650fcbdf.EVMVMBridgedToken_059a77239dafa770977dd9f1e98632c3e4559848.Vault \ # 0.001 \ -# --signer \ # --compute-limit 9999 \ -# --network testnet +# --network testnet \ +# --signer # # PYUSD0 # flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ From 7f88290435d310528972e4af8f3eeb07f5ab9fc1 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:15:57 -0500 Subject: [PATCH 30/31] revert FlowCreditMarket submodule update --- lib/FlowCreditMarket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FlowCreditMarket b/lib/FlowCreditMarket index 32dbf84..5da5a4f 160000 --- a/lib/FlowCreditMarket +++ b/lib/FlowCreditMarket @@ -1 +1 @@ -Subproject commit 32dbf8483a8d875625a5a9d285aeccc95518f946 +Subproject commit 5da5a4f4f6e2a45130566b79da9d2c9f3a955937 From 2c2baa0d0a489b365f539793c5841095b7461ce9 Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:32:59 -0500 Subject: [PATCH 31/31] mainnet config --- local/setup_mainnet.sh | 68 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/local/setup_mainnet.sh b/local/setup_mainnet.sh index e34e80c..5982c6d 100755 --- a/local/setup_mainnet.sh +++ b/local/setup_mainnet.sh @@ -37,34 +37,57 @@ cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transa # add WETH to band oracle cd ./lib/FlowCreditMarket/FlowActions && flow transactions send ./cadence/transactions/band-oracle-connector/add_symbol.cdc "ETH" "A.1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault" --network mainnet --signer mainnet-band-oracle-connectors && cd ../../.. -# add WBTC as supported token -flow transactions send ../lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_kink_curve.cdc \ +# WBTC simple curve +flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_simple_interest_curve.cdc \ 'A.1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault' \ 0.8 \ 1.0 \ - 0.8 \ - 0.0 \ - 0.04 \ - 0.4 \ 1_000_000.0 \ 1_000_000.0 \ --network mainnet \ --signer mainnet-flow-credit-market-deployer -# add WETH as supported token -flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_kink_curve.cdc \ +# WETH simple curve +flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_simple_interest_curve.cdc \ 'A.1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault' \ 0.8 \ 1.0 \ - 0.8 \ - 0.0 \ - 0.04 \ - 0.4 \ 1_000_000.0 \ 1_000_000.0 \ --network mainnet \ --signer mainnet-flow-credit-market-deployer +# kink interest curve setup +# enable when FCM_V1 is deployed +# +# # add WBTC as supported token +# flow transactions send ../lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_kink_curve.cdc \ +# 'A.1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault' \ +# 0.8 \ +# 1.0 \ +# 0.8 \ +# 0.0 \ +# 0.04 \ +# 0.4 \ +# 1_000_000.0 \ +# 1_000_000.0 \ +# --network mainnet \ +# --signer mainnet-flow-credit-market-deployer +# +# # add WETH as supported token +# flow transactions send ./lib/FlowCreditMarket/cadence/transactions/flow-credit-market/pool-governance/add_supported_token_kink_curve.cdc \ +# 'A.1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault' \ +# 0.8 \ +# 1.0 \ +# 0.8 \ +# 0.0 \ +# 0.04 \ +# 0.4 \ +# 1_000_000.0 \ +# 1_000_000.0 \ +# --network mainnet \ +# --signer mainnet-flow-credit-market-deployer + # TODO # swap # echo "swap Flow to MOET" @@ -207,7 +230,26 @@ flow transactions send ./lib/FlowCreditMarket/cadence/tests/transactions/flow-cr # --network mainnet \ # --signer # - +# +# WBTC (BTCf) +# flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ +# A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy \ +# A.1e4aa0b87d10b141.EVMVMBridgedToken_717dae2baf7656be9a9b01dee31d571a9d4c9579.Vault \ +# 0.0000001 \ +# --compute-limit 9999 \ +# --network mainnet \ +# --signer +# +# WETH (ETHf) +# flow transactions send ./cadence/transactions/flow-yield-vaults/create_yield_vault.cdc \ +# A.b1d63873c3cc9f79.FlowYieldVaultsStrategiesV1_1.mUSDFStrategy \ +# A.1e4aa0b87d10b141.EVMVMBridgedToken_2f6f07cdcf3588944bf4c42ac74ff24bf56e7590.Vault \ +# 0.00001 \ +# --compute-limit 9999 \ +# --network mainnet \ +# --signer +# +# # test PEAK MONEY strategy # # WFLOW