Smart Contract
ERC4626SwapConnectors
A.04f5ae6bef48c1fc.ERC4626SwapConnectors
1import Burner from 0xf233dcee88fe0abe
2import FungibleToken from 0xf233dcee88fe0abe
3import EVM from 0xe467b9dd11fa00df
4import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
5import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
6import DeFiActions from 0x6d888f175c158410
7import DeFiActionsUtils from 0x6d888f175c158410
8import ERC4626SinkConnectors from 0x04f5ae6bef48c1fc
9import SwapConnectors from 0xe1a479f0cb911df9
10import EVMTokenConnectors from 0x1a771b21fcceadc2
11import ERC4626Utils from 0x04f5ae6bef48c1fc
12
13/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
14/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
15/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16///
17/// ERC4626SwapConnectors
18///
19/// Implements the DeFiActions.Swapper interface to swap asset tokens to 4626 shares, integrating the connector with an
20/// EVM ERC4626 Vault.
21///
22access(all) contract ERC4626SwapConnectors {
23
24 /// Swapper
25 ///
26 /// An implementation of the DeFiActions.Swapper interface to swap assets to 4626 shares where the input token is
27 /// underlying asset in the 4626 vault. Both the asset & the 4626 shares must be onboarded to the VM bridge in order
28 /// for liquidity to flow between Cadnece & EVM. These "swaps" are performed by depositing the input asset into the
29 /// ERC4626 vault and withdrawing the resulting shares from the ERC4626 vault.
30 ///
31 /// NOTE: Since ERC4626 vaults typically do not support synchronous withdrawals, this Swapper only supports the
32 /// default inType -> outType path via swap() and reverts on swapBack() since the withdrawal cannot be returned
33 /// synchronously.
34 ///
35 access(all) struct Swapper : DeFiActions.Swapper {
36 /// The asset type serving as the price basis in the ERC4626 vault
37 access(self) let asset: Type
38 /// The EVM address of the asset ERC20 asset underlying the ERC4626 vault
39 access(self) let assetEVMAddress: EVM.EVMAddress
40 /// The address of the ERC4626 vault
41 access(self) let vault: EVM.EVMAddress
42 /// The type of the bridged ERC4626 vault
43 access(self) let vaultType: Type
44 /// The token sink to use for the ERC4626 vault
45 access(self) let assetSink: ERC4626SinkConnectors.AssetSink
46 /// The token source to use for the ERC4626 vault
47 access(self) let shareSource: EVMTokenConnectors.Source
48 /// The optional UniqueIdentifier of the ERC4626 vault
49 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
50
51 init(
52 asset: Type,
53 vault: EVM.EVMAddress,
54 coa: Capability<auth(EVM.Call, EVM.Bridge) &EVM.CadenceOwnedAccount>,
55 feeSource: {DeFiActions.Sink, DeFiActions.Source},
56 uniqueID: DeFiActions.UniqueIdentifier?
57 ) {
58 pre {
59 DeFiActionsUtils.definingContractIsFungibleToken(asset):
60 "Provided asset \(asset.identifier) is not a Vault type"
61 coa.check():
62 "Provided COA Capability is invalid - need Capability<&EVM.CadenceOwnedAccount>"
63 }
64 self.asset = asset
65 self.assetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: asset)
66 ?? panic("Provided asset \(asset.identifier) is not associated with ERC20 - ensure the type & ERC20 contracts are associated via the VM bridge")
67 self.vault = vault
68 self.vaultType = FlowEVMBridgeConfig.getTypeAssociated(with: vault)
69 ?? panic("Provided ERC4626 Vault \(vault.toString()) is not associated with a Cadence FungibleToken - ensure the type & ERC4626 contracts are associated via the VM bridge")
70
71 self.assetSink = ERC4626SinkConnectors.AssetSink(
72 asset: asset,
73 vault: vault,
74 coa: coa,
75 feeSource: feeSource,
76 uniqueID: uniqueID
77 )
78 self.shareSource = EVMTokenConnectors.Source(
79 min: nil,
80 withdrawVaultType: self.vaultType,
81 coa: coa,
82 feeSource: feeSource,
83 uniqueID: uniqueID
84 )
85
86 self.uniqueID = uniqueID
87 }
88
89 /// The type of Vault this Swapper accepts when performing a swap
90 access(all) view fun inType(): Type {
91 return self.asset
92 }
93 /// The type of Vault this Swapper provides when performing a swap
94 access(all) view fun outType(): Type {
95 return self.vaultType
96 }
97 /// The estimated amount required to provide a Vault with the desired output balance
98 access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} {
99 if reverse { // unsupported
100 return SwapConnectors.BasicQuote(
101 inType: self.vaultType,
102 outType: self.asset,
103 inAmount: 0.0,
104 outAmount: 0.0
105 )
106 }
107 let uintForDesired = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(forDesired, erc20Address: self.vault)
108 if let uintRequired = ERC4626Utils.previewMint(vault: self.vault, shares: uintForDesired) {
109 let uintMaxAllowed = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(UFix64.max, erc20Address: self.assetEVMAddress)
110
111 if uintRequired > uintMaxAllowed {
112 return SwapConnectors.BasicQuote(
113 inType: self.asset,
114 outType: self.vaultType,
115 inAmount: UFix64.max,
116 outAmount: forDesired
117 )
118 }
119 let ufixRequired = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(uintRequired, erc20Address: self.assetEVMAddress)
120 return SwapConnectors.BasicQuote(
121 inType: self.asset,
122 outType: self.vaultType,
123 inAmount: ufixRequired,
124 outAmount: forDesired
125 )
126 }
127 return SwapConnectors.BasicQuote(
128 inType: self.asset,
129 outType: self.vaultType,
130 inAmount: 0.0,
131 outAmount: 0.0
132 )
133 }
134 /// The estimated amount delivered out for a provided input balance
135 access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} {
136 if reverse { // unsupported
137 return SwapConnectors.BasicQuote(
138 inType: self.vaultType,
139 outType: self.asset,
140 inAmount: 0.0,
141 outAmount: 0.0
142 )
143 }
144
145 // ensure the provided amount is not greater than the maximum deposit capacity
146 let maxCapacity = self.assetSink.minimumCapacity()
147 var _forProvided = forProvided
148 if maxCapacity == 0.0 {
149 _forProvided = 0.0
150 } else if _forProvided > maxCapacity {
151 _forProvided = maxCapacity
152 }
153 let uintForProvided = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(_forProvided, erc20Address: self.assetEVMAddress)
154
155
156 if let uintShares = ERC4626Utils.previewDeposit(vault: self.vault, assets: uintForProvided) {
157 let ufixShares = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(uintShares, erc20Address: self.vault)
158 return SwapConnectors.BasicQuote(
159 inType: self.asset,
160 outType: self.vaultType,
161 inAmount: _forProvided,
162 outAmount: ufixShares
163 )
164 }
165 return SwapConnectors.BasicQuote(
166 inType: self.asset,
167 outType: self.vaultType,
168 inAmount: 0.0,
169 outAmount: 0.0
170 )
171 }
172 /// Performs a swap taking a Vault of type inVault, outputting a resulting outVault. Implementations may choose
173 /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
174 /// to use multiple Flow swap protocols.
175 access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
176 if inVault.balance == 0.0 {
177 // nothing to swap - burn the inVault and return an empty outVault
178 Burner.burn(<-inVault)
179 return <- DeFiActionsUtils.getEmptyVault(self.vaultType)
180 }
181
182 // assign or get the quote for the swap
183 let _quote = quote ?? self.quoteOut(forProvided: inVault.balance, reverse: false)
184
185 // get the before available shares
186 let beforeAvailable = self.shareSource.minimumAvailable()
187
188 // deposit the inVault into the asset sink
189 self.assetSink.depositCapacity(from: &inVault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
190 Burner.burn(<-inVault)
191
192 // get the after available shares
193 let afterAvailable = self.shareSource.minimumAvailable()
194 assert(afterAvailable > beforeAvailable, message: "Expected ERC4626 Vault \(self.vault.toString()) to have more shares after depositing")
195
196 // withdraw the available difference in shares
197 let availableDiff = afterAvailable - beforeAvailable
198 let sharesVault <- self.shareSource.withdrawAvailable(maxAmount: availableDiff)
199 return <- sharesVault
200 }
201 /// Performs a swap taking a Vault of type outVault, outputting a resulting inVault. Implementations may choose
202 /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
203 /// to use multiple Flow swap protocols.
204 // TODO: Impl detail - accept quote that was just used by swap() but reverse the direction assuming swap() was just called
205 access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
206 panic("ERC4626SwapConnectors.Swapper.swapBack() is not supported - ERC4626 Vaults do not support synchronous withdrawals")
207 }
208 /// Returns a ComponentInfo struct containing information about this component and a list of ComponentInfo for
209 /// each inner component in the stack.
210 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
211 return DeFiActions.ComponentInfo(
212 type: self.getType(),
213 id: self.id(),
214 innerComponents: [
215 self.assetSink.getComponentInfo(),
216 self.shareSource.getComponentInfo()
217 ]
218 )
219 }
220 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
221 /// a DeFiActions stack. See DeFiActions.align() for more information.
222 ///
223 /// @return a copy of the struct's UniqueIdentifier
224 ///
225 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
226 return self.uniqueID
227 }
228 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
229 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
230 ///
231 /// @param id: the UniqueIdentifier to set for this component
232 ///
233 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
234 self.uniqueID = id
235 }
236 }
237}
238