Smart Contract
MockSwapper
A.b1d63873c3cc9f79.MockSwapper
1import FungibleToken from 0xf233dcee88fe0abe
2import Burner from 0xf233dcee88fe0abe
3
4import MockOracle from 0xb1d63873c3cc9f79
5
6import DeFiActions from 0x6d888f175c158410
7import SwapConnectors from 0xe1a479f0cb911df9
8import FlowALPMath from 0x6b00ff876c299c61
9
10///
11/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION
12/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
13///
14access(all) contract MockSwapper {
15
16 /// Mocked liquidity sources
17 access(self) let liquidityConnectors: {Type: {DeFiActions.Sink, DeFiActions.Source}}
18
19 /// Mock setter enabling the configuration of liquidity sources used by mock swappers
20 access(all) fun setLiquidityConnector(_ connector: {DeFiActions.Sink, DeFiActions.Source}) {
21 pre {
22 connector.getSinkType() == connector.getSourceType():
23 "Connector sink Type \(connector.getSinkType().identifier) != connector source Type \(connector.getSourceType().identifier)"
24 }
25 self.liquidityConnectors[connector.getSinkType()] = connector
26 }
27
28 // Swapper
29 //
30 /// Mocked DeFiActions Swapper implementation. Be sure to set connectors for Vaults you wish to handle via this mock
31 /// in MockSwapper.liquidityConnectors via .setLiquidityConnector before instantiating mocks
32 access(all) struct Swapper : DeFiActions.Swapper {
33 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
34 access(self) let inVault: Type
35 access(self) let outVault: Type
36 access(self) let oracle: {DeFiActions.PriceOracle}
37
38 init(inVault: Type, outVault: Type, uniqueID: DeFiActions.UniqueIdentifier?) {
39 pre {
40 MockSwapper.liquidityConnectors[inVault] != nil:
41 "Invalid inVault - \(inVault.identifier) does not have a MockSwapper connector to handle funds"
42 MockSwapper.liquidityConnectors[outVault] != nil:
43 "Invalid outVault - \(outVault.identifier) does not have a MockSwapper connector to handle funds"
44 }
45 self.inVault = inVault
46 self.outVault = outVault
47 self.oracle = MockOracle.PriceOracle()
48 self.uniqueID = uniqueID
49 }
50
51 /// The type of Vault this Swapper accepts when performing a swap
52 access(all) view fun inType(): Type {
53 return self.inVault
54 }
55
56 /// The type of Vault this Swapper provides when performing a swap
57 access(all) view fun outType(): Type {
58 return self.outVault
59 }
60
61 /// The estimated amount required to provide a Vault with the desired output balance, sourcing pricing from the
62 /// mocked oracle
63 access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} {
64 return self._estimate(amount: forDesired, out: false, reverse: reverse)
65 }
66
67 /// The estimated amount delivered out for a provided input balance, sourcing pricing from the mocked oracle
68 access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} {
69 return self._estimate(amount: forProvided, out: true, reverse: reverse)
70 }
71
72 /// Performs a swap taking a Vault of type inVault, outputting a resulting outVault. Implementations may choose
73 /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
74 /// to use multiple Flow swap protocols.
75 /// NOTE: This mock sources pricing data from the mocked oracle, allowing for pricing to be manually manipulated
76 /// for testing and demonstration purposes
77 access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
78 return <- self._swap(<-inVault, reverse: false)
79 }
80
81 /// Performs a swap taking a Vault of type outVault, outputting a resulting inVault. Implementations may choose
82 /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
83 /// to use multiple Flow swap protocols.
84 /// NOTE: This mock sources pricing data from the mocked oracle, allowing for pricing to be manually manipulated
85 /// for testing and demonstration purposes
86 access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
87 return <- self._swap(<-residual, reverse: true)
88 }
89
90 /// Internal estimator returning a quote for the amount in/out and in the desired direction
91 access(self) fun _estimate(amount: UFix64, out: Bool, reverse: Bool): {DeFiActions.Quote} {
92 let outTokenPrice = self.oracle.price(ofToken: self.outType())
93 ?? panic("Price for token \(self.outType().identifier) is currently unavailable")
94 let inTokenPrice = self.oracle.price(ofToken: self.inType())
95 ?? panic("Price for token \(self.inType().identifier) is currently unavailable")
96
97 let uintOutTokenPrice = UFix128(outTokenPrice)
98 let uintInTokenPrice = UFix128(inTokenPrice)
99
100 // the original formula is correct, but lacks precision
101 // let price = reverse ? outTokenPrice / inTokenPrice : inTokenPrice / outTokenPrice
102 let uintPrice = reverse ? (uintOutTokenPrice / uintInTokenPrice) : (uintInTokenPrice / uintOutTokenPrice)
103
104 if amount == UFix64.max {
105 return SwapConnectors.BasicQuote(
106 inType: reverse ? self.outType() : self.inType(),
107 outType: reverse ? self.inType() : self.outType(),
108 inAmount: UFix64.max,
109 outAmount: UFix64.max
110 )
111 }
112
113 let uintAmount = UFix128(amount)
114 let uintInAmount = out ? uintAmount : (uintAmount / uintPrice)
115 let uintOutAmount = out ? uintAmount * uintPrice : uintAmount
116
117 let inAmount = FlowALPMath.toUFix64Round(uintInAmount)
118 let outAmount = FlowALPMath.toUFix64Round(uintOutAmount)
119
120 return SwapConnectors.BasicQuote(
121 inType: reverse ? self.outVault : self.inVault,
122 outType: reverse ? self.inVault : self.outVault,
123 inAmount: inAmount,
124 outAmount: outAmount
125 )
126 }
127
128 access(self) fun _swap(_ from: @{FungibleToken.Vault}, reverse: Bool): @{FungibleToken.Vault} {
129 let inAmount = from.balance
130 var swapInVault = reverse ? MockSwapper.liquidityConnectors[from.getType()]! : MockSwapper.liquidityConnectors[self.inType()]!
131 var swapOutVault = reverse ? MockSwapper.liquidityConnectors[self.inType()]! : MockSwapper.liquidityConnectors[self.outType()]!
132 swapInVault.depositCapacity(from: &from as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
133 Burner.burn(<-from)
134 let outAmount = self.quoteOut(forProvided: inAmount, reverse: reverse).outAmount
135 var outVault <- swapOutVault.withdrawAvailable(maxAmount: outAmount)
136
137 assert(outVault.balance == outAmount,
138 message: "MockSwapper outVault returned invalid balance - expected \(outAmount), received \(outVault.balance)")
139
140 return <- outVault
141 }
142 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
143 return DeFiActions.ComponentInfo(
144 type: self.getType(),
145 id: self.id(),
146 innerComponents: []
147 )
148 }
149 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
150 return self.uniqueID
151 }
152 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
153 self.uniqueID = id
154 }
155 }
156
157 init() {
158 self.liquidityConnectors = {}
159 }
160}
161
162