Smart Contract
SwapConnectors
A.0bce04a00aedf132.SwapConnectors
1import Burner from 0xf233dcee88fe0abe
2import FungibleToken from 0xf233dcee88fe0abe
3
4import DeFiActions from 0x92195d814edf9cb0
5import DeFiActionsUtils from 0x92195d814edf9cb0
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.getType().isSubtype(of: Type<@{FungibleToken.Vault}>()):
92 "Invalid inVault type - \(inVault.identifier) is not a FungibleToken Vault implementation"
93 outVault.getType().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: true, 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] = [0.0, 0.0]
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: true).outAmount
195 : swapper.quoteIn(forDesired: amount, reverse: true).inAmount
196 if (out ? res[1] < estimate : estimate < res[1]) {
197 // take minimum for in, maximum for out
198 res = [UFix64(i), estimate]
199 }
200 }
201 return res
202 }
203 /// Swaps the provided Vault in the defined direction. If the quote is not a MultiSwapperQuote, a new quote is
204 /// requested and the current optimal Swapper used to fulfill the swap.
205 access(self) fun _swap(quote: {DeFiActions.Quote}?, from: @{FungibleToken.Vault}, reverse: Bool): @{FungibleToken.Vault} {
206 var multiQuote = quote as? MultiSwapperQuote
207 if multiQuote != nil || multiQuote!.swapperIndex > self.swappers.length {
208 multiQuote = self.quoteOut(forProvided: from.balance, reverse: reverse) as! MultiSwapperQuote
209 }
210 let optimalSwapper = &self.swappers[multiQuote!.swapperIndex] as &{DeFiActions.Swapper}
211 if reverse {
212 return <- optimalSwapper.swapBack(quote: multiQuote, residual: <-from)
213 } else {
214 return <- optimalSwapper.swap(quote: multiQuote, inVault: <-from)
215 }
216 }
217 }
218
219 /// SwapSink
220 ///
221 /// A DeFiActions connector that deposits the resulting post-conversion currency of a token swap to an inner
222 /// DeFiActions Sink, sourcing funds from a deposited Vault of a pre-set Type.
223 ///
224 access(all) struct SwapSink : DeFiActions.Sink {
225 access(self) let swapper: {DeFiActions.Swapper}
226 access(self) let sink: {DeFiActions.Sink}
227 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
228
229 init(swapper: {DeFiActions.Swapper}, sink: {DeFiActions.Sink}, uniqueID: DeFiActions.UniqueIdentifier?) {
230 pre {
231 swapper.outType() == sink.getSinkType():
232 "Swapper outputs \(swapper.outType().identifier) but Sink takes \(sink.getSinkType().identifier) - "
233 .concat("Ensure the provided Swapper outputs a Vault Type compatible with the provided Sink")
234 }
235 self.swapper = swapper
236 self.sink = sink
237 self.uniqueID = uniqueID
238 }
239
240 /// Returns a ComponentInfo struct containing information about this SwapSink and its inner DFA components
241 ///
242 /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
243 /// each inner component in the stack.
244 ///
245 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
246 return DeFiActions.ComponentInfo(
247 type: self.getType(),
248 id: self.id(),
249 innerComponents: [
250 self.swapper.getComponentInfo(),
251 self.sink.getComponentInfo()
252 ]
253 )
254 }
255 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
256 /// a DeFiActions stack. See DeFiActions.align() for more information.
257 ///
258 /// @return a copy of the struct's UniqueIdentifier
259 ///
260 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
261 return self.uniqueID
262 }
263 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
264 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
265 ///
266 /// @param id: the UniqueIdentifier to set for this component
267 ///
268 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
269 self.uniqueID = id
270 }
271 /// Returns the type of Vault this Sink accepts when performing a swap
272 ///
273 /// @return the type of Vault this Sink accepts when performing a swap
274 ///
275 access(all) view fun getSinkType(): Type {
276 return self.swapper.inType()
277 }
278 /// Returns the minimum capacity required to deposit to this Sink
279 ///
280 /// @return the minimum capacity required to deposit to this Sink
281 ///
282 access(all) fun minimumCapacity(): UFix64 {
283 return self.swapper.quoteIn(forDesired: self.sink.minimumCapacity(), reverse: false).inAmount
284 }
285 /// Deposits the provided Vault to this Sink, swapping the provided Vault to the required type if necessary
286 ///
287 /// @param from: the Vault to source deposits from
288 ///
289 access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
290 let limit = self.sink.minimumCapacity()
291 if from.balance == 0.0 || limit == 0.0 || from.getType() != self.getSinkType() {
292 return // nothing to swap from, no capacity to ingest, invalid Vault type - do nothing
293 }
294
295 let quote = self.swapper.quoteIn(forDesired: limit, reverse: false)
296 let swapVault <- from.createEmptyVault()
297 if from.balance <= quote.inAmount {
298 // sink can accept all of the available tokens, so we swap everything
299 swapVault.deposit(from: <-from.withdraw(amount: from.balance))
300 } else {
301 // sink is limited to fewer tokens than we have available - swap the amount we need to meet the limit
302 swapVault.deposit(from: <-from.withdraw(amount: quote.inAmount))
303 }
304
305 // swap then deposit to the inner sink
306 let swappedTokens <- self.swapper.swap(quote: quote, inVault: <-swapVault)
307 self.sink.depositCapacity(from: &swappedTokens as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
308
309 if swappedTokens.balance > 0.0 {
310 // swap back any residual to the originating vault
311 let residual <- self.swapper.swapBack(quote: nil, residual: <-swappedTokens)
312 from.deposit(from: <-residual)
313 } else {
314 Burner.burn(<-swappedTokens) // nothing left - burn & execute vault's burnCallback()
315 }
316 }
317 }
318
319 /// SwapSource
320 ///
321 /// A DeFiActions connector that returns post-conversion currency, sourcing pre-converted funds from an inner
322 /// DeFiActions Source
323 ///
324 access(all) struct SwapSource : DeFiActions.Source {
325 access(self) let swapper: {DeFiActions.Swapper}
326 access(self) let source: {DeFiActions.Source}
327 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
328
329 init(swapper: {DeFiActions.Swapper}, source: {DeFiActions.Source}, uniqueID: DeFiActions.UniqueIdentifier?) {
330 pre {
331 source.getSourceType() == swapper.inType():
332 "Source outputs \(source.getSourceType().identifier) but Swapper takes \(swapper.inType().identifier) - "
333 .concat("Ensure the provided Source outputs a Vault Type compatible with the provided Swapper")
334 }
335 self.swapper = swapper
336 self.source = source
337 self.uniqueID = uniqueID
338 }
339
340 /// Returns a ComponentInfo struct containing information about this SwapSource and its inner DFA components
341 ///
342 /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
343 /// each inner component in the stack.
344 ///
345 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
346 return DeFiActions.ComponentInfo(
347 type: self.getType(),
348 id: self.id(),
349 innerComponents: [
350 self.swapper.getComponentInfo(),
351 self.source.getComponentInfo()
352 ]
353 )
354 }
355 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
356 /// a DeFiActions stack. See DeFiActions.align() for more information.
357 ///
358 /// @return a copy of the struct's UniqueIdentifier
359 ///
360 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
361 return self.uniqueID
362 }
363 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
364 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
365 ///
366 /// @param id: the UniqueIdentifier to set for this component
367 ///
368 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
369 self.uniqueID = id
370 }
371 /// Returns the type of Vault this Source provides when performing a swap
372 ///
373 /// @return the type of Vault this Source provides when performing a swap
374 ///
375 access(all) view fun getSourceType(): Type {
376 return self.swapper.outType()
377 }
378 /// Returns the minimum amount of currency available to withdraw from this Source
379 ///
380 /// @return the minimum amount of currency available to withdraw from this Source
381 ///
382 access(all) fun minimumAvailable(): UFix64 {
383 // estimate post-conversion currency based on the source's pre-conversion balance available
384 let availableIn = self.source.minimumAvailable()
385 return availableIn > 0.0
386 ? self.swapper.quoteOut(forProvided: availableIn, reverse: false).outAmount
387 : 0.0
388 }
389 /// Withdraws the provided amount of currency from this Source, swapping the provided amount to the required type if necessary
390 ///
391 /// @param maxAmount: the maximum amount of currency to withdraw from this Source
392 ///
393 /// @return the Vault containing the withdrawn currency
394 ///
395 access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} {
396 let minimumAvail = self.minimumAvailable()
397 if minimumAvail == 0.0 || maxAmount == 0.0 {
398 return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
399 }
400
401 // expect output amount as the lesser between the amount available and the maximum amount
402 var quote = minimumAvail < maxAmount
403 ? self.swapper.quoteOut(forProvided: self.source.minimumAvailable(), reverse: false)
404 : self.swapper.quoteIn(forDesired: maxAmount, reverse: false)
405
406 let sourceLiquidity <- self.source.withdrawAvailable(maxAmount: quote.inAmount)
407 if sourceLiquidity.balance == 0.0 {
408 Burner.burn(<-sourceLiquidity)
409 return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
410 }
411 let outVault <- self.swapper.swap(quote: quote, inVault: <-sourceLiquidity)
412 if outVault.balance > quote.outAmount {
413 // TODO - what to do if excess is found?
414 // - can swapBack() but can't deposit to the inner source and can't return an unsupported Vault type
415 // -> could make inner {Source} an intersection {Source, Sink}
416 }
417 return <- outVault
418 }
419 }
420}