Smart Contract
FlowEVMBridgeTokenEscrow
A.1e4aa0b87d10b141.FlowEVMBridgeTokenEscrow
1
2import Burner from 0xf233dcee88fe0abe
3import FungibleToken from 0xf233dcee88fe0abe
4import NonFungibleToken from 0x1d7e57aa55817448
5import MetadataViews from 0x1d7e57aa55817448
6import ViewResolver from 0x1d7e57aa55817448
7import FlowToken from 0x1654653399040a61
8
9import EVM from 0xe467b9dd11fa00df
10
11import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
12import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
13import CrossVMToken from 0x1e4aa0b87d10b141
14
15/// This escrow contract handles the locking of fungible tokens that are bridged from Cadence to EVM and retrieval of
16/// locked assets in escrow when they are bridged back to Cadence.
17///
18access(all) contract FlowEVMBridgeTokenEscrow {
19
20 /**********************
21 Getters
22 ***********************/
23
24 /// Returns whether the Locker has been initialized for the given fungible token type
25 ///
26 /// @param forType: Type of the locked fungible tokens
27 ///
28 /// @returns true if the Locker has been initialized for the given fungible token type, otherwise false
29 ///
30 access(all) view fun isInitialized(forType: Type): Bool {
31 return self.borrowLocker(forType: forType) != nil
32 }
33
34 /// Returns the balance of locked tokens for the given fungible token type
35 ///
36 /// @param tokenType: Type of the locked fungible tokens
37 ///
38 /// @returns The balance of locked tokens for the given fungible token type or nil if the locker doesn't exist
39 ///
40 access(all) view fun getLockedTokenBalance(tokenType: Type): UFix64? {
41 return self.borrowLocker(forType: tokenType)?.getLockedBalance() ?? nil
42 }
43
44 /// Returns the type of the locked vault for the given fungible token type
45 ///
46 /// @param tokenType: Type of the locked fungible tokens
47 ///
48 /// @returns The type of the locked vault for the given fungible token type or nil if the locker doesn't exist
49 ///
50 access(all) view fun getViews(tokenType: Type): [Type]? {
51 return self.borrowLocker(forType: tokenType)?.getViews() ?? []
52 }
53
54 /**********************
55 Bridge Methods
56 ***********************/
57
58 /// Initializes the Locker for the given fungible token type if it hasn't been initialized yet
59 ///
60 access(account) fun initializeEscrow(
61 with vault: @{FungibleToken.Vault},
62 name: String,
63 symbol: String,
64 decimals: UInt8,
65 evmTokenAddress: EVM.EVMAddress
66 ) {
67 pre {
68 vault.balance == 0.0:
69 "Vault contains a balance=".concat(vault.balance.toString())
70 .concat(" - can only initialize Escrow with an empty vault")
71 }
72 let lockedType = vault.getType()
73 let lockerPath = FlowEVMBridgeUtils.deriveEscrowStoragePath(fromType: lockedType)
74 ?? panic("Problem deriving Locker path for Vault type identifier=".concat(lockedType.identifier))
75 if self.account.storage.type(at: lockerPath) != nil {
76 panic("Token Locker already stored at storage path=".concat(lockedType.identifier))
77 }
78
79 // Create the Locker, lock a new vault of given type and save at the derived path
80 let locker <- create Locker(
81 name: name,
82 symbol: symbol,
83 decimals: decimals,
84 lockedVault: <-vault,
85 evmTokenAddress: evmTokenAddress
86 )
87 self.account.storage.save(<-locker, to: lockerPath)
88 }
89
90 /// Locks the fungible tokens in escrow returning the storage used by locking the Vault
91 ///
92 access(account) fun lockTokens(_ vault: @{FungibleToken.Vault}): UInt64 {
93 let locker = self.borrowLocker(forType: vault.getType())
94 ?? panic("Locker doesn't exist for given type=".concat(vault.getType().identifier))
95
96 let preStorageSnapshot = self.account.storage.used
97 locker.deposit(from: <-vault)
98 let postStorageSnapshot = self.account.storage.used
99
100 // Return the amount of storage used by the locker after storing the NFT
101 if postStorageSnapshot < preStorageSnapshot {
102 // Due to atree inlining, account storage usage may counterintuitively decrease at times - return 0
103 return 0
104 } else {
105 // Otherwise, return the storage usage delta
106 return postStorageSnapshot - preStorageSnapshot
107 }
108 }
109
110 /// Unlocks the tokens of the given type and amount, reverting if it isn't in escrow
111 ///
112 access(account) fun unlockTokens(type: Type, amount: UFix64): @{FungibleToken.Vault} {
113 let locker = self.borrowLocker(forType: type)
114 ?? panic("Locker doesn't exist for given type=".concat(type.identifier))
115 return <- locker.withdraw(amount: amount)
116
117 }
118
119 /// Retrieves an entitled locker for the given type or nil if it doesn't exist
120 ///
121 access(self) view fun borrowLocker(forType: Type): auth(FungibleToken.Withdraw) &Locker? {
122 if let lockerPath = FlowEVMBridgeUtils.deriveEscrowStoragePath(fromType: forType) {
123 if self.account.storage.type(at: lockerPath) == Type<@Locker>() {
124 return self.account.storage.borrow<auth(FungibleToken.Withdraw) &Locker>(from: lockerPath)
125 }
126 }
127 return nil
128 }
129
130 /*********************
131 Locker
132 *********************/
133
134 /// The resource managing the locking & unlocking of FTs via this contract's interface.
135 ///
136 access(all) resource Locker : CrossVMToken.EVMTokenInfo, FungibleToken.Receiver, FungibleToken.Provider, ViewResolver.Resolver {
137 /// Corresponding name assigned in the tokens' corresponding ERC20 contract
138 access(all) let name: String
139 /// Corresponding symbol assigned in the tokens' corresponding ERC20 contract
140 access(all) let symbol: String
141 /// Corresponding decimals assigned in the tokens' corresponding ERC20 contract. While Cadence support floating
142 /// point numbers, EVM does not, so we need to keep track of the decimals to convert between the two.
143 access(all) let decimals: UInt8
144 /// Corresponding ERC20 address for the locked tokens
145 access(all) let evmTokenAddress: EVM.EVMAddress
146 // Vault to hold all locked tokens
147 access(self) let lockedVault: @{FungibleToken.Vault}
148
149
150 init(name: String, symbol: String, decimals: UInt8, lockedVault: @{FungibleToken.Vault}, evmTokenAddress: EVM.EVMAddress) {
151 self.decimals = decimals
152 self.name = name
153 self.symbol = symbol
154 self.evmTokenAddress = evmTokenAddress
155
156 self.lockedVault <- lockedVault
157 // Locked Vaults must accept their own type as Lockers escrow Vaults on a 1:1 type basis
158 assert(
159 self.lockedVault.isSupportedVaultType(type: self.lockedVault.getType()),
160 message: "Locked Vault does not accept its own type=".concat(self.lockedVault.getType().identifier)
161 )
162 }
163
164 /// Gets the ERC20 name value
165 access(all) view fun getName(): String {
166 return self.name
167 }
168 /// Gets the ERC20 symbol value
169 access(all) view fun getSymbol(): String {
170 return self.symbol
171 }
172 /// Gets the ERC20 decimals value
173 access(all) view fun getDecimals(): UInt8 {
174 return self.decimals
175 }
176 /// Get the EVM contract address of the locked Vault's corresponding ERC20 contract address
177 ///
178 access(all) view fun getEVMContractAddress(): EVM.EVMAddress {
179 return self.evmTokenAddress
180 }
181
182 /// Returns the balance of tokens in the locked Vault
183 ///
184 access(all) view fun getLockedBalance(): UFix64 {
185 return self.lockedVault.balance
186 }
187
188 /// Returns the type of the locked vault
189 ///
190 access(all) view fun getLockedType(): Type {
191 return self.lockedVault.getType()
192 }
193
194 /// Function to ask a provider if a specific amount of tokens is available to be withdrawn from the locked vault
195 ///
196 access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
197 return self.lockedVault.isAvailableToWithdraw(amount: amount)
198 }
199
200 /// Returns a mapping of vault types that this locker accepts. This Locker will only accept Vaults of the same
201 ///
202 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
203 return {self.lockedVault.getType(): true}
204 }
205
206 /// Returns whether or not the given type is accepted by the Receiver
207 ///
208 access(all) view fun isSupportedVaultType(type: Type): Bool {
209 return self.getSupportedVaultTypes()[type] ?? false
210 }
211
212 /// Returns the views supported by the locked Vault
213 ///
214 access(all) view fun getViews(): [Type] {
215 return self.lockedVault.getViews()
216 }
217
218 /// Resolves the requested view type on the locked FT if it supports the requested view type
219 ///
220 access(all) fun resolveView(_ view: Type): AnyStruct? {
221 return self.lockedVault.resolveView(view)
222 }
223
224 /// Deposits the given token vault into the contained locked Vault
225 ///
226 access(all) fun deposit(from: @{FungibleToken.Vault}) {
227 self.lockedVault.deposit(from: <-from)
228 }
229
230 /// Withdraws an amount of tokens from this locker, removing it from the vault and returning it
231 ///
232 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
233 return <-self.lockedVault.withdraw(amount: amount)
234 }
235 }
236}
237