Smart Contract
EVMTokenConnectors
A.cc15a0c9c656b648.EVMTokenConnectors
1import EVM from 0xe467b9dd11fa00df
2import Burner from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4import FungibleToken from 0xf233dcee88fe0abe
5import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
6import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
7import FlowEVMBridge from 0x1e4aa0b87d10b141
8import DeFiActions from 0x92195d814edf9cb0
9import DeFiActionsUtils from 0x92195d814edf9cb0
10
11/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
13/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
14///
15/// EVMTokenConnectors
16///
17/// A collection of DeFiActions connectors that deposit/withdraw tokens to/from EVM addresses.
18/// NOTE: These connectors move FLOW to/from the COA's WFLOW balance, not it's native FLOW balance. See
19/// EVMNativeFlowConnectors for connectors that move FLOW to/from the COA's native FLOW balance.
20///
21access(all) contract EVMTokenConnectors {
22
23 /// Sink
24 ///
25 /// A DeFiActions connector that deposits tokens to an EVM address's balance of ERC20 tokens
26 /// NOTE: If FLOW is deposited, it affects the COA's WFLOW balance not it's native FLOW balance.
27 ///
28 access(all) struct Sink : DeFiActions.Sink {
29 /// The maximum balance of the COA, checked before executing a deposit
30 access(self) let maximumBalance: UFix64
31 /// The type of the Vault to deposit
32 access(self) let depositVaultType: Type
33 /// The EVM address of the linked COA
34 access(self) let address: EVM.EVMAddress
35 /// The source of the VM bridge fees, providing FLOW
36 access(self) let feeSource: {DeFiActions.Sink, DeFiActions.Source}
37 /// The unique identifier of the sink
38 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
39
40 init(
41 max: UFix64?,
42 depositVaultType: Type,
43 address: EVM.EVMAddress,
44 feeSource: {DeFiActions.Sink, DeFiActions.Source},
45 uniqueID: DeFiActions.UniqueIdentifier?
46 ) {
47 pre {
48 FlowEVMBridgeConfig.getEVMAddressAssociated(with: depositVaultType) != nil:
49 "Provided type \(depositVaultType.identifier) has not been onboarded to the VM bridge - "
50 .concat("Ensure the type & ERC20 contracts are associated via the VM bridge")
51 feeSource.getSinkType() == Type<@FlowToken.Vault>() && feeSource.getSourceType() == Type<@FlowToken.Vault>():
52 "Provided feeSource must provide FlowToken.Vault but provides \(feeSource.getSourceType().identifier)"
53 }
54 self.maximumBalance = max ?? UFix64.max // assume no maximum if none provided
55 self.depositVaultType = depositVaultType
56 self.address = address
57 self.feeSource = feeSource
58 self.uniqueID = uniqueID
59 }
60
61 /// Returns a ComponentInfo struct containing information about this Sink and its inner DFA components
62 ///
63 /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
64 /// each inner component in the stack.
65 ///
66 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
67 return DeFiActions.ComponentInfo(
68 type: self.getType(),
69 id: self.id(),
70 innerComponents: [
71 self.feeSource.getComponentInfo()
72 ]
73 )
74 }
75 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
76 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
77 ///
78 /// @param id: the UniqueIdentifier to set for this component
79 ///
80 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
81 self.uniqueID = id
82 }
83 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
84 /// a DeFiActions stack. See DeFiActions.align() for more information.
85 ///
86 /// @return a copy of the struct's UniqueIdentifier
87 ///
88 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
89 return self.uniqueID
90 }
91 /// Returns the type of the Vault this Sink accepts
92 ///
93 /// @return the type of the Vault this Sink accepts
94 ///
95 access(all) view fun getSinkType(): Type {
96 return self.depositVaultType
97 }
98 /// Returns the minimum capacity of this Sink
99 ///
100 /// @return the minimum capacity of this Sink
101 ///
102 access(all) fun minimumCapacity(): UFix64 {
103 let erc20Address = FlowEVMBridgeConfig.getEVMAddressAssociated(with: self.depositVaultType)!
104 let balance = FlowEVMBridgeUtils.balanceOf(owner: self.address, evmContractAddress: erc20Address)
105 let balanceInCadence = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(
106 balance,
107 erc20Address: erc20Address
108 )
109 return balanceInCadence < self.maximumBalance ? self.maximumBalance - balanceInCadence : 0.0
110 }
111 /// Deposits the given Vault into the EVM address's balance
112 ///
113 /// @param from: an authorized reference to the Vault from which to deposit funds
114 ///
115 access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
116 if from.getType() != self.depositVaultType {
117 return // unrelated vault type
118 }
119
120 // assess amount to deposit
121 let capacity = self.minimumCapacity()
122 let amount = from.balance > capacity ? capacity : from.balance
123 if amount == 0.0 {
124 return // can't deposit without sufficient capacity
125 }
126
127 // collect VM bridge fees
128 let feeAmount = FlowEVMBridgeConfig.baseFee * 2.0
129 if self.feeSource.minimumAvailable() < feeAmount {
130 return // early return here instead of reverting in bridge scope on insufficient fees
131 }
132 let fees <- self.feeSource.withdrawAvailable(maxAmount: feeAmount)
133
134 // deposit tokens and handle remaining fees
135 FlowEVMBridge.bridgeTokensToEVM(
136 vault: <-from.withdraw(amount: amount),
137 to: self.address,
138 feeProvider: &fees as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}
139 )
140 self._handleRemainingFees(<-fees)
141 }
142 /// Handles the remaining fees after a withdrawal
143 ///
144 /// @param feeVault: the Vault containing the remaining fees
145 ///
146 access(self) fun _handleRemainingFees(_ feeVault: @{FungibleToken.Vault}) {
147 if feeVault.balance > 0.0 {
148 self.feeSource.depositCapacity(from: &feeVault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
149 }
150 Burner.burn(<-feeVault)
151 }
152 }
153
154 /// Source
155 ///
156 /// A DeFiActions connector that withdraws tokens from a CadenceOwnedAccount's balance of ERC20 tokens
157 /// NOTE: If FLOW is withdrawn, it affects the COA's WFLOW balance not it's native FLOW balance.
158 ///
159 access(all) struct Source : DeFiActions.Source {
160 /// The minimum balance of the COA, checked before executing a withdrawal
161 access(self) let minimumBalance: UFix64
162 /// The type of the Vault to withdraw
163 access(self) let withdrawVaultType: Type
164 /// The COA to withdraw tokens from
165 access(self) let coa: Capability<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>
166 /// The EVM address of the linked COA
167 access(self) let address: EVM.EVMAddress
168 /// The source of the VM bridge fees, providing FLOW
169 access(self) let feeSource: {DeFiActions.Sink, DeFiActions.Source}
170 /// The unique identifier of the source
171 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
172
173 init(
174 min: UFix64?,
175 withdrawVaultType: Type,
176 coa: Capability<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>,
177 feeSource: {DeFiActions.Sink, DeFiActions.Source},
178 uniqueID: DeFiActions.UniqueIdentifier?
179 ) {
180 pre {
181 FlowEVMBridgeConfig.getEVMAddressAssociated(with: withdrawVaultType) != nil:
182 "Provided type \(withdrawVaultType.identifier) has not been onboarded to the VM bridge - "
183 .concat("Ensure the type & ERC20 contracts are associated via the VM bridge")
184 DeFiActionsUtils.definingContractIsFungibleToken(withdrawVaultType):
185 "The contract defining Vault \(withdrawVaultType.identifier) does not conform to FungibleToken contract interface"
186 coa.check():
187 "Provided COA Capability is invalid - provided an invalid Capability<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>"
188 feeSource.getSinkType() == Type<@FlowToken.Vault>() && feeSource.getSourceType() == Type<@FlowToken.Vault>():
189 "Provided feeSource must provide FlowToken.Vault but provides \(feeSource.getSourceType().identifier)"
190 }
191 self.minimumBalance = min ?? 0.0
192 self.withdrawVaultType = withdrawVaultType
193 self.coa = coa
194 self.feeSource = feeSource
195 self.address = coa.borrow()!.address()
196 self.uniqueID = uniqueID
197 }
198
199 /// Returns a ComponentInfo struct containing information about this Source and its inner DFA components
200 ///
201 /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
202 /// each inner component in the stack.
203 ///
204 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
205 return DeFiActions.ComponentInfo(
206 type: self.getType(),
207 id: self.id(),
208 innerComponents: []
209 )
210 }
211 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
212 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
213 ///
214 /// @param id: the UniqueIdentifier to set for this component
215 ///
216 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
217 self.uniqueID = id
218 }
219 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
220 /// a DeFiActions stack. See DeFiActions.align() for more information.
221 ///
222 /// @return a copy of the struct's UniqueIdentifier
223 ///
224 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
225 return self.uniqueID
226 }
227 /// Returns the type of the Vault this Source accepts
228 ///
229 /// @return the type of the Vault this Source accepts
230 ///
231 access(all) view fun getSourceType(): Type {
232 return self.withdrawVaultType
233 }
234 /// Returns the minimum available balance of this Source
235 ///
236 /// @return the minimum available balance of this Source
237 ///
238 access(all) fun minimumAvailable(): UFix64 {
239 if let coa = self.coa.borrow() {
240 let erc20Address = FlowEVMBridgeConfig.getEVMAddressAssociated(with: self.withdrawVaultType)!
241 let balance = FlowEVMBridgeUtils.balanceOf(owner: coa.address(), evmContractAddress: erc20Address)
242 let balanceInCadence = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(
243 balance,
244 erc20Address: erc20Address
245 )
246 return self.minimumBalance < balanceInCadence ? balanceInCadence - self.minimumBalance : 0.0
247 }
248 return 0.0
249 }
250 /// Withdraws the given amount of tokens from the CadenceOwnedAccount's balance of ERC20 tokens
251 ///
252 /// @param maxAmount: the maximum amount of tokens to withdraw
253 ///
254 /// @return a Vault containing the withdrawn tokens
255 ///
256 access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} {
257 let available = self.minimumAvailable()
258 let coa = self.coa.borrow()
259
260 // collect VM bridge fees
261 let feeAmount = FlowEVMBridgeConfig.baseFee
262 if available > 0.0 && coa != nil && self.feeSource.minimumAvailable() >= feeAmount {
263 // convert final cadence amount to erc20 amount
264 let ufixAmount = available > maxAmount ? maxAmount : available
265 let erc20Address = FlowEVMBridgeConfig.getEVMAddressAssociated(with: self.withdrawVaultType)!
266 let uintAmount = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(ufixAmount, erc20Address: erc20Address)
267
268 // withdraw tokens & handle fees
269 let fees <- self.feeSource.withdrawAvailable(maxAmount: feeAmount)
270 let tokens <- coa!.withdrawTokens(
271 type: self.getSourceType(),
272 amount: uintAmount,
273 feeProvider: &fees as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}
274 )
275 self._handleRemainingFees(<-fees)
276
277 return <- tokens
278 }
279
280 return <- DeFiActionsUtils.getEmptyVault(self.getSourceType())
281 }
282 /// Handles the remaining fees after a withdrawal
283 ///
284 /// @param feeVault: the Vault containing the remaining fees
285 ///
286 access(self) fun _handleRemainingFees(_ feeVault: @{FungibleToken.Vault}) {
287 if feeVault.balance > 0.0 {
288 self.feeSource.depositCapacity(from: &feeVault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
289 }
290 Burner.burn(<-feeVault)
291 }
292 }
293}
294