Smart Contract
StrategyExecutor
A.79f5b5b0f95a160b.StrategyExecutor
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import stFlowToken from 0xd6f80565193ad727
4import VaultCore from 0x79f5b5b0f95a160b
5import LiquidStaking from 0xd6f80565193ad727
6import LendingInterfaces from 0x2df970b6cdee5735
7import LendingComptroller from 0xf80cb737bfe7c792
8import LendingConfig from 0x2df970b6cdee5735
9import SwapRouter from 0xa6850776a94e6551
10import SwapInterfaces from 0xb78ef7afa52ff906
11
12/// Non-custodial strategy executor
13/// Executes strategies directly on vault funds without withdrawal
14access(all) contract StrategyExecutor {
15
16 access(all) event StrategyExecuted(strategy: String, inputAmount: UFix64, outputAmount: UFix64)
17 access(all) event YieldHarvested(strategy: String, amount: UFix64)
18
19 // Execute staking strategy on vault funds
20 access(all) fun executeStaking(vaultRef: &VaultCore.Vault, amount: UFix64) {
21 // Vault withdraws for strategy (internal operation)
22 let flowVault <- vaultRef.withdrawForStrategy(
23 assetType: VaultCore.AssetType.flow,
24 amount: amount
25 ) as! @FlowToken.Vault
26
27 // Stake FLOW -> stFLOW
28 let stFlowResult <- LiquidStaking.stake(flowVault: <-flowVault)
29 let resultAmount = stFlowResult.balance
30
31 // Return stFLOW directly to vault
32 vaultRef.depositFromStrategy(
33 assetType: VaultCore.AssetType.stflow,
34 from: <-stFlowResult
35 )
36
37 // Record yield
38 vaultRef.recordYieldHarvest(
39 assetType: VaultCore.AssetType.stflow,
40 amount: 0.0
41 )
42
43 emit StrategyExecuted(strategy: "Staking", inputAmount: amount, outputAmount: resultAmount)
44 }
45
46 // Execute looping strategy on vault funds
47 access(all) fun executeLooping(vaultRef: &VaultCore.Vault, amount: UFix64, loops: UInt8) {
48 pre {
49 loops >= 1 && loops <= 3: "Loops must be 1-3"
50 }
51
52 // Get user certificate for lending
53 let certificate <- LendingComptroller.IssueUserCertificate()
54
55 // Withdraw initial FLOW from vault
56 var flowVault <- vaultRef.withdrawForStrategy(
57 assetType: VaultCore.AssetType.flow,
58 amount: amount
59 ) as! @FlowToken.Vault
60
61 // Get pool references
62 let stFlowPool = getAccount(0x44fe3d9157770b2d).capabilities
63 .borrow<&{LendingInterfaces.PoolPublic}>(LendingConfig.PoolPublicPublicPath)
64 ?? panic("Cannot access stFlow pool")
65
66 let flowPool = getAccount(0x7492e2f9b4acea9a).capabilities
67 .borrow<&{LendingInterfaces.PoolPublic}>(LendingConfig.PoolPublicPublicPath)
68 ?? panic("Cannot access FLOW pool")
69
70 var totalStFlow: UFix64 = 0.0
71 var currentLoop: UInt8 = 0
72
73 // Execute loops
74 while currentLoop < loops {
75 let currentBalance = flowVault.balance
76
77 if currentBalance == 0.0 {
78 break
79 }
80
81 // Withdraw all FLOW for this loop iteration
82 let flowForThisLoop <- flowVault.withdraw(amount: currentBalance) as! @FlowToken.Vault
83 let flowAmount = flowForThisLoop.balance
84
85 // Stake FLOW -> stFLOW
86 let stFlowVault <- LiquidStaking.stake(flowVault: <-flowForThisLoop)
87 totalStFlow = totalStFlow + stFlowVault.balance
88
89 // Supply stFLOW as collateral
90 stFlowPool.supply(
91 supplierAddr: certificate.owner!.address,
92 inUnderlyingVault: <-stFlowVault
93 )
94
95 currentLoop = currentLoop + 1
96
97 // If not the last loop, borrow more FLOW
98 if currentLoop < loops {
99 let borrowAmount = flowAmount * 0.7
100
101 if borrowAmount > 0.0 {
102 let borrowedFlow <- flowPool.borrow(
103 userCertificate: &certificate as &{LendingInterfaces.IdentityCertificate},
104 borrowAmount: borrowAmount
105 ) as! @FlowToken.Vault
106
107 // Deposit borrowed FLOW back into our working vault
108 flowVault.deposit(from: <-borrowedFlow)
109 }
110 }
111 }
112
113 // Clean up
114 destroy flowVault
115 destroy certificate
116
117 // Record the leveraged position in vault
118 vaultRef.recordYieldHarvest(
119 assetType: VaultCore.AssetType.stflow,
120 amount: totalStFlow
121 )
122
123 emit StrategyExecuted(strategy: "Looping", inputAmount: amount, outputAmount: totalStFlow)
124 }
125
126 // Execute optimal swap strategy on vault funds
127 access(all) fun executeSwap(vaultRef: &VaultCore.Vault, amount: UFix64, fromAsset: VaultCore.AssetType, toAsset: VaultCore.AssetType) {
128 // Withdraw from vault for swap
129 let inputVault <- vaultRef.withdrawForStrategy(
130 assetType: fromAsset,
131 amount: amount
132 )
133
134 var outputVault: @{FungibleToken.Vault}? <- nil
135
136 if fromAsset == VaultCore.AssetType.flow && toAsset == VaultCore.AssetType.stflow {
137 // FLOW -> stFLOW optimal routing
138 let flowVault <- inputVault as! @FlowToken.Vault
139 let result <- self.optimalFlowToStFlow(flowVault: <-flowVault)
140 outputVault <-! result
141
142 } else if fromAsset == VaultCore.AssetType.stflow && toAsset == VaultCore.AssetType.flow {
143 // stFLOW -> FLOW optimal routing
144 let stFlowVault <- inputVault as! @stFlowToken.Vault
145 let result <- self.optimalStFlowToFlow(stFlowVault: <-stFlowVault)
146 outputVault <-! result
147
148 } else {
149 destroy inputVault
150 panic("Unsupported swap pair")
151 }
152
153 let outputAmount = outputVault?.balance ?? 0.0
154
155 // Return swapped funds to vault
156 vaultRef.depositFromStrategy(
157 assetType: toAsset,
158 from: <-outputVault!
159 )
160
161 emit StrategyExecuted(strategy: "Swap", inputAmount: amount, outputAmount: outputAmount)
162 }
163
164 // Optimal FLOW -> stFLOW routing
165 access(self) fun optimalFlowToStFlow(flowVault: @FlowToken.Vault): @stFlowToken.Vault {
166 let amount = flowVault.balance
167
168 // Check staking rate
169 let stakingRate = LiquidStaking.calcStFlowFromFlow(flowAmount: amount)
170
171 // Check DEX rates
172 var bestDexRate: UFix64 = 0.0
173 var bestDex: String = ""
174
175 let pairV1 = getAccount(0x396c0cda3302d8c5).capabilities
176 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
177 if pairV1 != nil {
178 let v1Rate = pairV1!.getAmountOut(
179 amountIn: amount,
180 tokenInKey: "A.1654653399040a61.FlowToken"
181 )
182 if v1Rate > bestDexRate {
183 bestDexRate = v1Rate
184 bestDex = "V1"
185 }
186 }
187
188 let pairStable = getAccount(0xc353b9d685ec427d).capabilities
189 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
190 if pairStable != nil {
191 let stableRate = pairStable!.getAmountOut(
192 amountIn: amount,
193 tokenInKey: "A.1654653399040a61.FlowToken"
194 )
195 if stableRate > bestDexRate {
196 bestDexRate = stableRate
197 bestDex = "Stable"
198 }
199 }
200
201 // Execute via best route
202 if stakingRate >= bestDexRate {
203 return <- LiquidStaking.stake(flowVault: <-flowVault)
204 } else if bestDex == "V1" {
205 return <- pairV1!.swap(vaultIn: <-flowVault, exactAmountOut: nil) as! @stFlowToken.Vault
206 } else {
207 return <- pairStable!.swap(vaultIn: <-flowVault, exactAmountOut: nil) as! @stFlowToken.Vault
208 }
209 }
210
211 // Optimal stFLOW -> FLOW routing
212 access(self) fun optimalStFlowToFlow(stFlowVault: @stFlowToken.Vault): @FlowToken.Vault {
213 let amount = stFlowVault.balance
214
215 // Check DEX rates (unstaking has delay, so DEX is preferred)
216 var bestRate: UFix64 = 0.0
217 var bestPair: &{SwapInterfaces.PairPublic}? = nil
218
219 let pairV1 = getAccount(0x396c0cda3302d8c5).capabilities
220 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
221 if pairV1 != nil {
222 let v1Rate = pairV1!.getAmountOut(
223 amountIn: amount,
224 tokenInKey: "A.d6f80565193ad727.stFlowToken"
225 )
226 if v1Rate > bestRate {
227 bestRate = v1Rate
228 bestPair = pairV1
229 }
230 }
231
232 let pairStable = getAccount(0xc353b9d685ec427d).capabilities
233 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
234 if pairStable != nil {
235 let stableRate = pairStable!.getAmountOut(
236 amountIn: amount,
237 tokenInKey: "A.d6f80565193ad727.stFlowToken"
238 )
239 if stableRate > bestRate {
240 bestRate = stableRate
241 bestPair = pairStable
242 }
243 }
244
245 if bestPair != nil {
246 return <- bestPair!.swap(vaultIn: <-stFlowVault, exactAmountOut: nil) as! @FlowToken.Vault
247 } else {
248 // Fallback to SwapRouter
249 let deadline = getCurrentBlock().timestamp + 300.0
250 return <- SwapRouter.swapExactTokensForTokens(
251 exactVaultIn: <-stFlowVault,
252 amountOutMin: 0.0,
253 tokenKeyPath: [
254 "A.d6f80565193ad727.stFlowToken",
255 "A.1654653399040a61.FlowToken"
256 ],
257 deadline: deadline
258 ) as! @FlowToken.Vault
259 }
260 }
261
262 // Harvest yields from strategies back to vault
263 access(all) fun harvestYields(vaultRef: &VaultCore.Vault, strategyType: String) {
264 var harvestedAmount: UFix64 = 0.0
265
266 switch strategyType {
267 case "Lending":
268 harvestedAmount = 0.0
269
270 case "Farming":
271 harvestedAmount = 0.0
272
273 default:
274 log("Unknown strategy type")
275 }
276
277 if harvestedAmount > 0.0 {
278 vaultRef.recordYieldHarvest(
279 assetType: VaultCore.AssetType.flow,
280 amount: harvestedAmount
281 )
282 emit YieldHarvested(strategy: strategyType, amount: harvestedAmount)
283 }
284 }
285}