Smart Contract
SwapConnectors
A.e1a479f0cb911df9.SwapConnectors
1import Burner from 0xf233dcee88fe0abe
2import FungibleToken from 0xf233dcee88fe0abe
3
4import DeFiActions from 0x6d888f175c158410
5import DeFiActionsUtils from 0x6d888f175c158410
6
7/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
8/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
9/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10///
11/// SwapConnectors
12///
13/// This contract defines DeFi Actions Sink & Source connector implementations for use with DeFi protocols. These
14/// connectors can be used alone or in conjunction with other DeFi Actions connectors to create complex DeFi workflows.
15///
16access(all) contract SwapConnectors {
17
18 /// BasicQuote
19 ///
20 /// A simple implementation of DeFiActions.Quote allowing callers of Swapper.quoteIn() and .quoteOut() to cache quoted
21 /// amount in and/or out.
22 ///
23 access(all) struct BasicQuote : DeFiActions.Quote {
24 access(all) let inType: Type
25 access(all) let outType: Type
26 access(all) let inAmount: UFix64
27 access(all) let outAmount: UFix64
28
29 init(
30 inType: Type,
31 outType: Type,
32 inAmount: UFix64,
33 outAmount: UFix64
34 ) {
35 self.inType = inType
36 self.outType = outType
37 self.inAmount = inAmount
38 self.outAmount = outAmount
39 }
40 }
41
42 /// MultiSwapperQuote
43 ///
44 /// A MultiSwapper specific DeFiActions.Quote implementation allowing for callers to set the Swapper used in
45 /// MultiSwapper that should fulfill the Swap
46 ///
47 access(all) struct MultiSwapperQuote : DeFiActions.Quote {
48 access(all) let inType: Type
49 access(all) let outType: Type
50 access(all) let inAmount: UFix64
51 access(all) let outAmount: UFix64
52 access(all) let swapperIndex: Int
53
54 init(
55 inType: Type,
56 outType: Type,
57 inAmount: UFix64,
58 outAmount: UFix64,
59 swapperIndex: Int
60 ) {
61 pre {
62 swapperIndex >= 0: "Invalid swapperIndex - provided \(swapperIndex) is less than 0"
63 }
64 self.inType = inType
65 self.outType = outType
66 self.inAmount = inAmount
67 self.outAmount = outAmount
68 self.swapperIndex = swapperIndex
69 }
70 }
71
72 /// MultiSwapper
73 ///
74 /// A Swapper implementation routing swap requests to the optimal contained Swapper. Once constructed, this can
75 /// effectively be used as an aggregator across all contained Swapper implementations, though it is limited to the
76 /// routes and pools exposed by its inner Swappers as well as runtime computation limits.
77 ///
78 access(all) struct MultiSwapper : DeFiActions.Swapper {
79 access(all) let swappers: [{DeFiActions.Swapper}]
80 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
81 access(self) let inVault: Type
82 access(self) let outVault: Type
83
84 init(
85 inVault: Type,
86 outVault: Type,
87 swappers: [{DeFiActions.Swapper}],
88 uniqueID: DeFiActions.UniqueIdentifier?
89 ) {
90 pre {
91 inVault.isSubtype(of: Type<@{FungibleToken.Vault}>()):
92 "Invalid inVault type - \(inVault.identifier) is not a FungibleToken Vault implementation"
93 outVault.isSubtype(of: Type<@{FungibleToken.Vault}>()):
94 "Invalid outVault type - \(outVault.identifier) is not a FungibleToken Vault implementation"
95 }
96 for i in InclusiveRange(0, swappers.length - 1) {
97 let swapper = &swappers[i] as &{DeFiActions.Swapper}
98 assert(swapper.inType() == inVault,
99 message: "Mismatched inVault \(inVault.identifier) - Swapper \(swapper.getType().identifier) accepts \(swapper.inType().identifier)")
100 assert(swapper.outType() == outVault,
101 message: "Mismatched outVault \(outVault.identifier) - Swapper \(swapper.getType().identifier) accepts \(swapper.outType().identifier)")
102 }
103 self.inVault = inVault
104 self.outVault = outVault
105 self.uniqueID = uniqueID
106 self.swappers = swappers
107 }
108
109 /// Returns a ComponentInfo struct containing information about this MultiSwapper and its inner DFA components
110 ///
111 /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
112 /// each inner component in the stack.
113 ///
114 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
115 let inner: [DeFiActions.ComponentInfo] = []
116 for swapper in self.swappers {
117 inner.append(swapper.getComponentInfo())
118 }
119 return DeFiActions.ComponentInfo(
120 type: self.getType(),
121 id: self.id(),
122 innerComponents: inner
123 )
124 }
125 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
126 /// a DeFiActions stack. See DeFiActions.align() for more information.
127 ///
128 /// @return a copy of the struct's UniqueIdentifier
129 ///
130 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
131 return self.uniqueID
132 }
133 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
134 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
135 ///
136 /// @param id: the UniqueIdentifier to set for this component
137 ///
138 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
139 self.uniqueID = id
140 }
141 /// The type of Vault this Swapper accepts when performing a swap
142 access(all) view fun inType(): Type {
143 return self.inVault
144 }
145 /// The type of Vault this Swapper provides when performing a swap
146 access(all) view fun outType(): Type {
147 return self.outVault
148 }
149 /// The estimated amount required to provide a Vault with the desired output balance
150 access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} {
151 let estimate = self._estimate(amount: forDesired, out: false, reverse: reverse)
152 return MultiSwapperQuote(
153 inType: reverse ? self.outType() : self.inType(),
154 outType: reverse ? self.inType() : self.outType(),
155 inAmount: estimate[1],
156 outAmount: forDesired,
157 swapperIndex: Int(estimate[0])
158 )
159 }
160 /// The estimated amount delivered out for a provided input balance
161 access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} {
162 let estimate = self._estimate(amount: forProvided, out: true, reverse: reverse)
163 return MultiSwapperQuote(
164 inType: reverse ? self.outType() : self.inType(),
165 outType: reverse ? self.inType() : self.outType(),
166 inAmount: forProvided,
167 outAmount: estimate[1],
168 swapperIndex: Int(estimate[0])
169 )
170 }
171 /// Performs a swap taking a Vault of type inVault, outputting a resulting outVault. Implementations may choose
172 /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
173 /// to use multiple Flow swap protocols. If the provided quote is not a MultiSwapperQuote, a new quote is
174 /// requested and the optimal Swapper used to fulfill the swap.
175 /// NOTE: providing a Quote does not guarantee the fulfilled swap will enforce the quote's defined outAmount
176 access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
177 return <-self._swap(quote: quote, from: <-inVault, reverse: false)
178 }
179 /// Performs a swap taking a Vault of type outVault, outputting a resulting inVault. Implementations may choose
180 /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
181 /// to use multiple Flow swap protocols.
182 /// NOTE: providing a Quote does not guarantee the fulfilled swap will enforce the quote's defined outAmount
183 access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
184 return <-self._swap(quote: quote, from: <-residual, reverse: true)
185 }
186 /// Returns the the index of the optimal Swapper (result[0]) and the associated amountOut or amountIn (result[0])
187 /// as a UFix64 array
188 access(self) fun _estimate(amount: UFix64, out: Bool, reverse: Bool): [UFix64; 2] {
189 var res: [UFix64; 2] = out ? [0.0, 0.0] : [0.0, UFix64.max] // maximizing for out, minimizing for in
190 for i in InclusiveRange(0, self.swappers.length - 1) {
191 let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
192 // call the appropriate estimator
193 let estimate = out
194 ? swapper.quoteOut(forProvided: amount, reverse: reverse).outAmount
195 : swapper.quoteIn(forDesired: amount, reverse: reverse).inAmount
196 // estimates of 0.0 are presumed to be graceful failures - skip them
197 if estimate > 0.0 && (out ? res[1] < estimate : estimate < res[1]) {
198 // take minimum for in, maximum for out
199 res = [UFix64(i), estimate]
200 }
201 }
202 return res
203 }
204 /// Swaps the provided Vault in the defined direction. If the quote is not a MultiSwapperQuote, a new quote is
205 /// requested and the current optimal Swapper used to fulfill the swap.
206 access(self) fun _swap(quote: {DeFiActions.Quote}?, from: @{FungibleToken.Vault}, reverse: Bool): @{FungibleToken.Vault} {
207 var multiQuote = quote as? MultiSwapperQuote
208 if multiQuote == nil || multiQuote!.swapperIndex >= self.swappers.length {
209 multiQuote = self.quoteOut(forProvided: from.balance, reverse: reverse) as! MultiSwapperQuote
210 }
211 let optimalSwapper = &self.swappers[multiQuote!.swapperIndex] as &{DeFiActions.Swapper}
212 if reverse {
213 return <- optimalSwapper.swapBack(quote: multiQuote, residual: <-from)
214 } else {
215 return <- optimalSwapper.swap(quote: multiQuote, inVault: <-from)
216 }
217 }
218 }
219
220 /// SwapSink
221 ///
222 /// A DeFiActions connector that deposits the resulting post-conversion currency of a token swap to an inner
223 /// DeFiActions Sink, sourcing funds from a deposited Vault of a pre-set Type.
224 ///
225 /// NOTE: If residual tokens remain after depositing to the inner sink, swapBack() is called on the swapper to
226 /// return them to the original vault. One-way swappers (e.g., ERC4626SwapConnectors.Swapper) that do not
227 /// support swapBack() can still be used safely if the inner sink always consumes all swapped tokens (i.e.,
228 /// has sufficient capacity). If residuals occur with a one-way swapper, the transaction will revert.
229 ///
230 access(all) struct SwapSink : DeFiActions.Sink {
231 access(self) let swapper: {DeFiActions.Swapper}
232 access(self) let sink: {DeFiActions.Sink}
233 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
234
235 init(swapper: {DeFiActions.Swapper}, sink: {DeFiActions.Sink}, uniqueID: DeFiActions.UniqueIdentifier?) {
236 pre {
237 swapper.outType() == sink.getSinkType():
238 "Swapper outputs \(swapper.outType().identifier) but Sink takes \(sink.getSinkType().identifier) - "
239 .concat("Ensure the provided Swapper outputs a Vault Type compatible with the provided Sink")
240 }
241 self.swapper = swapper
242 self.sink = sink
243 self.uniqueID = uniqueID
244 }
245
246 /// Returns a ComponentInfo struct containing information about this SwapSink and its inner DFA components
247 ///
248 /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
249 /// each inner component in the stack.
250 ///
251 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
252 return DeFiActions.ComponentInfo(
253 type: self.getType(),
254 id: self.id(),
255 innerComponents: [
256 self.swapper.getComponentInfo(),
257 self.sink.getComponentInfo()
258 ]
259 )
260 }
261 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
262 /// a DeFiActions stack. See DeFiActions.align() for more information.
263 ///
264 /// @return a copy of the struct's UniqueIdentifier
265 ///
266 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
267 return self.uniqueID
268 }
269 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
270 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
271 ///
272 /// @param id: the UniqueIdentifier to set for this component
273 ///
274 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
275 self.uniqueID = id
276 }
277 /// Returns the type of Vault this Sink accepts when performing a swap
278 ///
279 /// @return the type of Vault this Sink accepts when performing a swap
280 ///
281 access(all) view fun getSinkType(): Type {
282 return self.swapper.inType()
283 }
284 /// Returns the minimum capacity required to deposit to this Sink
285 ///
286 /// @return the minimum capacity required to deposit to this Sink
287 ///
288 access(all) fun minimumCapacity(): UFix64 {
289 return self.swapper.quoteIn(forDesired: self.sink.minimumCapacity(), reverse: false).inAmount
290 }
291 /// Deposits the provided Vault to this Sink, swapping the provided Vault to the required type if necessary
292 ///
293 /// @param from: the Vault to source deposits from
294 ///
295 access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
296 // nothing to swap from, no capacity to ingest, invalid Vault type - do nothing
297 if from.balance == 0.0 || from.getType() != self.getSinkType() { return; }
298 let limit = self.sink.minimumCapacity()
299 if limit == 0.0 { return; }
300
301 // quote based on available funds & inner sink limit
302 var quote = self.swapper.quoteOut(forProvided: from.balance, reverse: false)
303 if limit <= quote.outAmount {
304 quote = self.swapper.quoteIn(forDesired: limit, reverse: false)
305 }
306 // error when quoting - in/out should not be 0.0
307 if quote.inAmount == 0.0 || quote.outAmount == 0.0 { return; }
308
309 // assign the amount to swap based on updated quote
310 let swapAmount = quote.inAmount <= from.balance ? quote.inAmount : from.balance
311 // swap then deposit to the inner sink
312 let swapVault <- from.withdraw(amount: swapAmount)
313 let swappedTokens <- self.swapper.swap(quote: quote, inVault: <-swapVault)
314 self.sink.depositCapacity(from: &swappedTokens as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
315
316 if swappedTokens.balance > 0.0 {
317 // swap back any residual to the originating vault
318 let residual <- self.swapper.swapBack(quote: nil, residual: <-swappedTokens)
319 from.deposit(from: <-residual)
320 } else {
321 Burner.burn(<-swappedTokens) // nothing left - burn & execute vault's burnCallback()
322 }
323 }
324 }
325
326 /// SequentialSwapper
327 ///
328 /// A Swapper implementation routing swap requests to a sequence of Swappers. This may be helpful when a single
329 /// path is not sufficient to meet the desired swap requirements - e.g. swapping from one token to the underlying
330 /// asset of an ERC4626 vault to then swap to the ERC4626 vault shares.
331 ///
332 access(all) struct SequentialSwapper : DeFiActions.Swapper {
333 access(all) let swappers: [{DeFiActions.Swapper}]
334 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
335 access(self) let inVault: Type
336 access(self) let outVault: Type
337
338 init(swappers: [{DeFiActions.Swapper}], uniqueID: DeFiActions.UniqueIdentifier?) {
339 pre {
340 swappers.length > 1:
341 "Provided swappers must have a length of at least 2 - provided \(swappers.length) swappers"
342 }
343 // ensure each swapper's inType matches the previous swapper's outType
344 for i in InclusiveRange(0, swappers.length - 1) {
345 let swapper = &swappers[i] as &{DeFiActions.Swapper}
346 if i < swappers.length - 1 {
347 let nextSwapper = &swappers[i + 1] as &{DeFiActions.Swapper}
348 assert(swapper.outType() == nextSwapper.inType(),
349 message: "Mismatched inType \(nextSwapper.inType().identifier) - Swapper \(swapper.getType().identifier) accepts \(swapper.outType().identifier)")
350 }
351 }
352 self.inVault = swappers[0].inType()
353 self.outVault = swappers[swappers.length - 1].outType()
354 self.swappers = swappers
355 self.uniqueID = uniqueID
356 }
357
358 /// Returns a ComponentInfo struct containing information about this SequentialSwapper and its inner DFA components
359 ///
360 /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
361 /// each inner component in the stack.
362 ///
363 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
364 let inner: [DeFiActions.ComponentInfo] = []
365 for i in InclusiveRange(0, self.swappers.length - 1) {
366 let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
367 inner.append(swapper.getComponentInfo())
368 }
369 return DeFiActions.ComponentInfo(type: self.getType(), id: self.id(), innerComponents: inner)
370 }
371 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
372 /// a DeFiActions stack. See DeFiActions.align() for more information.
373 ///
374 /// @return a copy of the struct's UniqueIdentifier
375 ///
376 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
377 return self.uniqueID
378 }
379 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
380 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
381 ///
382 /// @param id: the UniqueIdentifier to set for this component
383 ///
384 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
385 self.uniqueID = id
386 }
387 /// The type of Vault this Swapper accepts when performing a swap
388 access(all) view fun inType(): Type {
389 return self.inVault
390 }
391 /// The type of Vault this Swapper provides when performing a swap
392 access(all) view fun outType(): Type {
393 return self.outVault
394 }
395 /// The estimated amount required to provide a Vault with the desired output balance
396 ///
397 /// @param forDesired: The amount in desired of the pre-conversion currency as a result of the swap
398 /// @param reverse: If false, the default inVault -> outVault is used, otherwise, the method estimates a swap
399 /// in the opposite direction, outVault -> inVault
400 ///
401 /// @return a DeFiActions.Quote containing estimate data. In order to prevent upstream reversion,
402 /// result.inAmount and result.outAmount will be 0.0 if an estimate is not available
403 ///
404 access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} {
405 var tmpAmount = forDesired
406 var finalOutAmount = forDesired
407
408 // walk through the swappers in the sequence starting with the output based on direction of swap
409 let range = reverse
410 ? InclusiveRange(0, self.swappers.length - 1)
411 : InclusiveRange(self.swappers.length - 1, 0, step: -1)
412 for i in range {
413 let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
414 let quote = swapper.quoteIn(forDesired: tmpAmount, reverse: reverse)
415 if quote.inAmount == 0.0 || quote.outAmount == 0.0 {
416 return SwapConnectors.BasicQuote(
417 inType: reverse ? self.outType() : self.inType(),
418 outType: reverse ? self.inType() : self.outType(),
419 inAmount: 0.0,
420 outAmount: 0.0
421 )
422 }
423 if i == range.start { finalOutAmount = quote.outAmount; }
424 tmpAmount = quote.inAmount
425 }
426
427 return SwapConnectors.BasicQuote(
428 inType: reverse ? self.outType() : self.inType(),
429 outType: reverse ? self.inType() : self.outType(),
430 inAmount: tmpAmount,
431 outAmount: finalOutAmount
432 )
433 }
434 /// The estimated amount delivered out for a provided input balance
435 ///
436 /// @param forProvided: The amount in provided of the pre-conversion currency as a result of the swap
437 /// @param reverse: If false, the default inVault -> outVault is used, otherwise, the method estimates a swap
438 /// in the opposite direction, outVault -> inVault
439 ///
440 /// @return a DeFiActions.Quote containing estimate data. In order to prevent upstream reversion,
441 /// result.inAmount and result.outAmount will be 0.0 if an estimate is not available
442 ///
443 access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} {
444 var tmpAmount = forProvided
445 var finalInAmount = forProvided
446
447 // walk through the swappers in the sequence starting with the input based on direction of swap
448 let range = reverse
449 ? InclusiveRange(self.swappers.length - 1, 0, step: -1)
450 : InclusiveRange(0, self.swappers.length - 1)
451 for i in range {
452 let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
453 let quote = swapper.quoteOut(forProvided: tmpAmount, reverse: reverse)
454 if quote.inAmount == 0.0 || quote.outAmount == 0.0 {
455 return SwapConnectors.BasicQuote(
456 inType: reverse ? self.outType() : self.inType(),
457 outType: reverse ? self.inType() : self.outType(),
458 inAmount: 0.0,
459 outAmount: 0.0
460 )
461 }
462 if i == range.start { finalInAmount = quote.inAmount; }
463 tmpAmount = quote.outAmount
464 }
465
466 return BasicQuote(
467 inType: reverse ? self.outType() : self.inType(),
468 outType: reverse ? self.inType() : self.outType(),
469 inAmount: finalInAmount,
470 outAmount: tmpAmount
471 )
472 }
473 /// Performs a swap taking a Vault of type inVault, outputting a resulting outVault. Callers should be careful
474 /// to ensure the resulting output meets their expectations given the provided inVault.balance.
475 /// NOTE: providing a Quote does not guarantee the fulfilled swap will enforce the quote's defined outAmount
476 ///
477 /// @param quote: A `DeFiActions.Quote` data structure. This implementation ignores the quote parameter so callers
478 /// should be sure to enforce outputs against the expected outVault.balance
479 /// @param inVault: The Vault to swap from
480 ///
481 /// @return a Vault of type `outVault` containing the swapped currency.
482 ///
483 access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
484 return <- self._swap(quote: quote, from: <-inVault, reverse: false)
485 }
486 /// Performs a swap taking a Vault of type outVault, outputting a resulting inVault. Callers should be careful
487 /// to ensure the resulting output meets their expectations given the provided residual.balance.
488 /// NOTE: providing a Quote does not guarantee the fulfilled swap will enforce the quote's defined outAmount
489 ///
490 /// @param quote: A `DeFiActions.Quote` data structure. This implementation ignores the quote parameter so callers
491 /// should be sure to enforce outputs against the expected inVault.balance
492 /// @param residual: The Vault to swap back from
493 ///
494 /// @return a Vault of type `inVault` containing the swapped currency.
495 ///
496 access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
497 return <- self._swap(quote: quote, from: <-residual, reverse: true)
498 }
499 /// Internal swap function performing the swap along the defined path.
500 ///
501 /// @param quote: A `DeFiActions.Quote` data structure. If provided, quote.outAmount is used as the minimum amount out
502 /// desired otherwise a new quote is generated from current state
503 /// @param from: The Vault to swap from
504 /// @param reverse: Whether to swap in the reverse direction
505 ///
506 /// @return a Vault of type `outVault` containing the swapped currency.
507 ///
508 access(self)
509 fun _swap(quote: {DeFiActions.Quote}?, from: @{FungibleToken.Vault}, reverse: Bool): @{FungibleToken.Vault} {
510 let range = reverse
511 ? InclusiveRange(self.swappers.length - 1, 0, step: -1)
512 : InclusiveRange(0, self.swappers.length - 1)
513 var vault <- from.withdraw(amount: from.balance)
514 Burner.burn(<-from)
515
516 for i in range {
517 let swapper = &self.swappers[i] as &{DeFiActions.Swapper}
518 let swapFun = reverse ? swapper.swapBack : swapper.swap
519
520 let preSwap <- vault.withdraw(amount: vault.balance)
521 var swapped <- swapFun(quote: nil, <-preSwap)
522 vault <-> swapped
523 Burner.burn(<-swapped)
524 }
525 return <- vault
526 }
527 }
528
529 /// SwapSource
530 ///
531 /// A DeFiActions connector that returns post-conversion currency, sourcing pre-converted funds from an inner
532 /// DeFiActions Source
533 ///
534 access(all) struct SwapSource : DeFiActions.Source {
535 access(self) let swapper: {DeFiActions.Swapper}
536 access(self) let source: {DeFiActions.Source}
537 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
538
539 init(swapper: {DeFiActions.Swapper}, source: {DeFiActions.Source}, uniqueID: DeFiActions.UniqueIdentifier?) {
540 pre {
541 source.getSourceType() == swapper.inType():
542 "Source outputs \(source.getSourceType().identifier) but Swapper takes \(swapper.inType().identifier) - "
543 .concat("Ensure the provided Source outputs a Vault Type compatible with the provided Swapper")
544 }
545 self.swapper = swapper
546 self.source = source
547 self.uniqueID = uniqueID
548 }
549
550 /// Returns a ComponentInfo struct containing information about this SwapSource and its inner DFA components
551 ///
552 /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
553 /// each inner component in the stack.
554 ///
555 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
556 return DeFiActions.ComponentInfo(
557 type: self.getType(),
558 id: self.id(),
559 innerComponents: [
560 self.swapper.getComponentInfo(),
561 self.source.getComponentInfo()
562 ]
563 )
564 }
565 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
566 /// a DeFiActions stack. See DeFiActions.align() for more information.
567 ///
568 /// @return a copy of the struct's UniqueIdentifier
569 ///
570 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
571 return self.uniqueID
572 }
573 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
574 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
575 ///
576 /// @param id: the UniqueIdentifier to set for this component
577 ///
578 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
579 self.uniqueID = id
580 }
581 /// Returns the type of Vault this Source provides when performing a swap
582 ///
583 /// @return the type of Vault this Source provides when performing a swap
584 ///
585 access(all) view fun getSourceType(): Type {
586 return self.swapper.outType()
587 }
588 /// Returns the minimum amount of currency available to withdraw from this Source
589 ///
590 /// @return the minimum amount of currency available to withdraw from this Source
591 ///
592 access(all) fun minimumAvailable(): UFix64 {
593 // estimate post-conversion currency based on the source's pre-conversion balance available
594 let availableIn = self.source.minimumAvailable()
595 return availableIn > 0.0
596 ? self.swapper.quoteOut(forProvided: availableIn, reverse: false).outAmount
597 : 0.0
598 }
599 /// Withdraws the provided amount of currency from this Source, swapping the provided amount to the required type if necessary
600 ///
601 /// @param maxAmount: the maximum amount of currency to withdraw from this Source
602 ///
603 /// @return the Vault containing the withdrawn currency
604 ///
605 access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} {
606 let minimumAvail = self.minimumAvailable()
607 if minimumAvail == 0.0 || maxAmount == 0.0 {
608 return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
609 }
610
611 // expect output amount as the lesser between the amount available and the maximum amount
612 var quote = minimumAvail < maxAmount
613 ? self.swapper.quoteOut(forProvided: self.source.minimumAvailable(), reverse: false)
614 : self.swapper.quoteIn(forDesired: maxAmount, reverse: false)
615
616 let sourceLiquidity <- self.source.withdrawAvailable(maxAmount: quote.inAmount)
617 if sourceLiquidity.balance == 0.0 {
618 Burner.burn(<-sourceLiquidity)
619 return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
620 }
621 return <- self.swapper.swap(quote: quote, inVault: <-sourceLiquidity)
622 }
623 }
624}
625