Smart Contract
BlackHole
A.4396883a58c3a2d1.BlackHole
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# Black Hole is the utility contract for burning fungible tokens on the Flow blockchain.
5
6## Features:
7
8- You can register a BlackHole Resource from the BlackHole contract.
9- Users can burn fungible tokens by sending them to the random BlackHole Resource.
10- Users can get the balance of vanished fungible tokens by the type of the Fungible Token in the BlackHole Resource.
11
12*/
13import FungibleToken from 0xf233dcee88fe0abe
14import NonFungibleToken from 0x1d7e57aa55817448
15import ViewResolver from 0x1d7e57aa55817448
16import StringUtils from 0xa340dc0a4ec828ab
17// IncrementFi Swap
18import SwapConfig from 0xb78ef7afa52ff906
19import SwapInterfaces from 0xb78ef7afa52ff906
20
21/// BlackHole contract
22///
23access(all) contract BlackHole {
24
25 /* --- Entitlement --- */
26
27 // NOTHING
28
29 /* --- Events --- */
30
31 /// Event emitted when a new BlackHole Resource is registered
32 access(all) event NewBlackHoleRegistered(
33 blackHoleAddr: Address,
34 blackHoleId: UInt64,
35 )
36
37 /// Event emitted when a new Fungible Token is registered
38 access(all) event FungibleTokenVanished(
39 blackHoleAddr: Address,
40 blackHoleId: UInt64,
41 vaultIdentifier: Type,
42 amount: UFix64,
43 )
44
45 /// Event emitted when a new NonFungible Token is registered
46 access(all) event NonFungibleTokenVanished(
47 blackHoleAddr: Address,
48 blackHoleId: UInt64,
49 nftIdentifier: Type,
50 nftID: UInt64,
51 )
52
53 /* --- Variable, Enums and Structs --- */
54
55 /// BlackHole Resource
56 access(all) let storagePath: StoragePath
57 /// BlackHoles Registry
58 access(contract) let blackHoles: {Address: Bool}
59
60 /* --- Interfaces & Resources --- */
61
62 /// The public interface for the BlackHole Resource
63 ///
64 access(all) resource interface BlackHolePublic {
65 /// Check if the BlackHole Resource is valid
66 /// Valid means that the owner's account should have all keys revoked
67 ///
68 access(all)
69 view fun isValid(): Bool {
70 /// The Keys in the owner's account should be all revoked
71 if let ownerAddr = self.owner?.address {
72 let ownerAcct = getAccount(ownerAddr)
73 // Check if all keys are revoked
74 var isAllKeyRevoked = true
75 let totalKeyAmount = Int(ownerAcct.keys.count)
76 var i = 0
77 while i < totalKeyAmount {
78 if let key = ownerAcct.keys.get(keyIndex: i) {
79 isAllKeyRevoked = isAllKeyRevoked && key.isRevoked
80 }
81 i = i + 1
82 }
83 // TODO: Check no owned account (Hybrid custodial account)
84
85 return isAllKeyRevoked
86 }
87 return false
88 }
89 }
90
91 /// The resource of BlackHole Fungible Token Receiver
92 ///
93 access(all) resource Receiver: FungibleToken.Receiver, BlackHolePublic {
94 /// The dictionary of Fungible Token Pools
95 access(self) let pools: @{Type: {FungibleToken.Vault}}
96
97 init() {
98 self.pools <- {}
99 }
100
101 /** ---- FungibleToken Receiver Interface ---- */
102
103 /// Takes a Vault and deposits it into the implementing resource type
104 ///
105 /// @param from: The Vault resource containing the funds that will be deposited
106 ///
107 access(all)
108 fun deposit(from: @{FungibleToken.Vault}) {
109 pre {
110 self.isValid(): "The BlackHole Resource should be valid"
111 from.balance > 0.0: "The balance should be greater than zero"
112 }
113 let blackHoleAddr = self.owner?.address ?? panic("Invalid BlackHole Address")
114
115 // get basic information
116 let fromType = from.getType()
117 let vanishedAmount = from.balance
118
119 // should be A.{address}.{contractName}.Vault
120 let fromIdentifierArr = StringUtils.split(fromType.identifier, ".")
121 // check if the from vault is an IncrementFi LP
122 if fromIdentifierArr[2] == "SwapPair" {
123 let pairAddr = Address.fromString("0x".concat(fromIdentifierArr[1]))!
124 // @deprecated in Cadence 1.0
125 if let pairPubRef = getAccount(pairAddr)
126 .capabilities
127 .borrow<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath) {
128 if pairPubRef.getLpTokenVaultType() == fromType {
129 // Now we can confirm that the from vault is an IncrementFi LP
130 // check if there is a LP Collection in the BlackHole Account
131 if let lpTokenCollectionRef = getAccount(blackHoleAddr)
132 .capabilities
133 .borrow<&{SwapInterfaces.LpTokenCollectionPublic}>(SwapConfig.LpTokenCollectionPublicPath) {
134 // Deposit the LP Token into the LP Collection
135 lpTokenCollectionRef.deposit(pairAddr: pairAddr, lpTokenVault: <- from)
136
137 emit BlackHole.FungibleTokenVanished(
138 blackHoleAddr: blackHoleAddr,
139 blackHoleId: self.uuid,
140 vaultIdentifier: fromType,
141 amount: vanishedAmount
142 )
143 return
144 }
145 }
146 }
147 }
148 // Deposit the Fungible Token into the BlackHole Vault
149 let receiverRef = self._borrowOrCreateBlackHoleVault(fromType)
150 receiverRef.deposit(from: <- from)
151
152 emit BlackHole.FungibleTokenVanished(
153 blackHoleAddr: blackHoleAddr,
154 blackHoleId: self.uuid,
155 vaultIdentifier: fromType,
156 amount: vanishedAmount
157 )
158 }
159
160 /// getSupportedVaultTypes returns a dictionary of Vault types
161 /// and whether the type is currently supported by this Receiver
162 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
163 // All Vault types are supported by default, so return an empty dictionary
164 return {}
165 }
166
167 /// Returns whether or not the given type is accepted by the Receiver
168 /// A vault that can accept any type should just return true by default
169 access(all) view fun isSupportedVaultType(type: Type): Bool {
170 return true
171 }
172
173 /** ---- BlackHolePublic Interface ---- */
174
175 /// Get the balance by the type of the Fungible Token
176 ///
177 access(all)
178 view fun getVanishedBalance(_ type: Type): UFix64 {
179 return self.pools[type]?.balance ?? 0.0
180 }
181
182 /** ---- Internal Methods ---- */
183
184 /// Borrow the FungibleToken Vault
185 ///
186 access(self)
187 fun _borrowOrCreateBlackHoleVault(_ type: Type): &{FungibleToken.Vault} {
188 pre {
189 type.isSubtype(of: Type<@{FungibleToken.Vault}>()): "The type should be a subtype of FungibleToken.Vault"
190 }
191 if let ref = &self.pools[type] as &{FungibleToken.Vault}? {
192 return ref
193 } else {
194 let ftArr = StringUtils.split(type.identifier, ".")
195 let ftAddress = Address.fromString("0x".concat(ftArr[1])) ?? panic("Invalid Fungible Token Address")
196 let ftContractName = ftArr[2]
197 let ftContract = getAccount(ftAddress)
198 .contracts.borrow<&{FungibleToken}>(name: ftContractName)
199 ?? panic("Could not borrow the FungibleToken contract reference")
200 // @deprecated in Cadence 1.0
201 self.pools[type] <-! ftContract.createEmptyVault(vaultType: type)
202 return &self.pools[type] as &{FungibleToken.Vault}? ?? panic("Invalid Fungible Token Vault")
203 }
204 }
205 }
206
207 /// The resource of BlackHole NonFungible Token Collection
208 ///
209 access(all) resource Collection: NonFungibleToken.Collection, BlackHolePublic {
210 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
211 /// The dictionary of NonFungible Token Pools
212 /// NFT Type -> [NFT ID]
213 access(self) let nftOriginIds: {Type: [UInt64]}
214 access(self) let nftIdPrefix: {Type: UInt64}
215 access(self) let supportedNftTypes: {Type: Bool}
216
217 init() {
218 self.ownedNFTs <- {}
219 self.nftOriginIds = {}
220 self.nftIdPrefix = {}
221 self.supportedNftTypes = {}
222 }
223
224 /// --- NonFungibleToken Collection Interface ---
225
226 access(NonFungibleToken.Withdraw)
227 fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
228 panic("This function is invalid for the BlackHole Collection")
229 }
230
231 /// getSupportedNFTTypes returns a dictionary of NFT types
232 /// It returns all types of the vanished NFTs
233 access(all)
234 view fun getSupportedNFTTypes(): {Type: Bool} {
235 return self.supportedNftTypes
236 }
237
238 /// All NFT types are supported by default, so return true by default
239 ///
240 access(all)
241 view fun isSupportedNFTType(type: Type): Bool {
242 return true
243 }
244
245 /// deposit takes a NFT as an argument and stores it in the collection
246 /// @param token: The NFT to deposit into the collection
247 access(all)
248 fun deposit(token: @{NonFungibleToken.NFT}) {
249 pre {
250 self.isValid(): "The BlackHole Resource should be valid"
251 }
252
253 let nftType = token.getType()
254 var nftIdPrefix: UInt64 = 0
255 if self.nftIdPrefix[nftType] != nil {
256 nftIdPrefix = self.nftIdPrefix[nftType]!
257 } else {
258 nftIdPrefix = (UInt64(self.nftIdPrefix.keys.length) + 1) << 32
259 self.nftIdPrefix[nftType] = nftIdPrefix
260 }
261
262 if self.nftOriginIds[nftType] == nil {
263 self.nftOriginIds[nftType] = []
264 }
265 let nftIds = &self.nftOriginIds[nftType] as auth(Mutate) &[UInt64]? ?? panic("Invalid NFT Origin IDs")
266 let nftIdToVanish: UInt64 = token.id
267 // Add the NFT ID to the NFT Origin IDs
268 nftIds.append(nftIdToVanish)
269
270 // Store the NFT in the collection
271 let newNFTid = nftIdPrefix + nftIdToVanish
272 let toDestory <- self.ownedNFTs[newNFTid] <- token
273 destroy toDestory
274
275 // Set the NFTType as supported
276 if self.supportedNftTypes[nftType] == nil {
277 self.supportedNftTypes[nftType] = true
278 }
279
280 emit BlackHole.NonFungibleTokenVanished(
281 blackHoleAddr: self.owner?.address ?? panic("Invalid BlackHole Address"),
282 blackHoleId: self.uuid,
283 nftIdentifier: nftType,
284 nftID: nftIdToVanish
285 )
286 }
287
288 access(all)
289 view fun getLength(): Int {
290 return self.ownedNFTs.length
291 }
292
293 access(all)
294 view fun getIDs(): [UInt64] {
295 return self.ownedNFTs.keys
296 }
297
298 access(all)
299 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
300 return &self.ownedNFTs[id]
301 }
302
303 /// Borrow the view resolver for the specified NFT ID
304 access(all)
305 view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
306 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
307 return nft as &{ViewResolver.Resolver}
308 }
309 return nil
310 }
311
312 access(all)
313 fun createEmptyCollection(): @{NonFungibleToken.Collection} {
314 return <- create Collection()
315 }
316
317 /// --- BlackHolePublic Interface ---
318
319 /// Get the balance by the type of the Fungible Token
320 ///
321 access(all)
322 view fun getVanishedAmount(_ type: Type): Int {
323 return self.nftOriginIds[type]?.length ?? 0
324 }
325 }
326
327 /** --- Methods --- */
328
329 /// Get the receiver path for the BlackHole Resource
330 ///
331 /// @return The PublicPath for the generic BlackHole receiver
332 ///
333 access(all)
334 view fun getBlackHoleReceiverPublicPath(): PublicPath {
335 return /public/BlackHoleFTReceiver
336 }
337
338 /// Get the storage path for the BlackHole Resource
339 ///
340 /// @return The StoragePath for the generic BlackHole receiver
341 ///
342 access(all)
343 view fun getBlackHoleReceiverStoragePath(): StoragePath {
344 return self.storagePath
345 }
346
347 /// Create a new BlackHole Resource
348 ///
349 access(all)
350 fun createNewBlackHole(): @Receiver {
351 return <- create Receiver()
352 }
353
354 /// Register an address as a new BlackHole
355 ///
356 access(all)
357 fun registerAsBlackHole(_ addr: Address) {
358 if self.blackHoles[addr] == nil {
359 let ref = self.borrowBlackHoleReceiver(addr)
360 ?? panic("Could not borrow the BlackHole Resource")
361 assert(
362 ref.isValid(),
363 message: "The BlackHole Resource should be valid"
364 )
365 self.blackHoles[addr] = true
366
367 // emit the event
368 emit NewBlackHoleRegistered(
369 blackHoleAddr: addr,
370 blackHoleId: ref.uuid
371 )
372 }
373 }
374
375 /// Borrow a BlackHole Resource by the address
376 ///
377 access(all)
378 view fun borrowBlackHoleReceiver(_ addr: Address): &{FungibleToken.Receiver, BlackHolePublic}? {
379 return getAccount(addr)
380 .capabilities.borrow<&Receiver>(self.getBlackHoleReceiverPublicPath())
381 }
382
383 /// Check if is the address a valid BlackHole address
384 ///
385 access(all)
386 view fun isValidBlackHole(_ addr: Address): Bool {
387 return self.borrowBlackHoleReceiver(addr)?.isValid() == true
388 }
389
390 /// Register a BlackHole Resource
391 ///
392 access(all)
393 fun borrowRandomBlackHoleReceiver(): &{FungibleToken.Receiver, BlackHolePublic} {
394 let max = UInt64(self.blackHoles.keys.length)
395 assert(max > 0, message: "There is no BlackHole Resource")
396 let rand = revertibleRandom<UInt64>()
397 let blackHoleAddr = self.blackHoles.keys[rand % max]
398 return self.borrowBlackHoleReceiver(blackHoleAddr) ?? panic("Could not borrow the BlackHole Resource")
399 }
400
401 /// Get the registered BlackHoles addresses
402 ///
403 access(all)
404 view fun getRegisteredBlackHoles(): [Address] {
405 return self.blackHoles.keys
406 }
407
408 /// Check if there is any BlackHole Resource available
409 ///
410 access(all)
411 view fun isAnyBlackHoleAvailable(): Bool {
412 return self.blackHoles.keys.length > 0
413 }
414
415 /// Burn the Fungible Token by sending it to the BlackHole Resource
416 ///
417 access(all)
418 fun vanish(_ vault: @{FungibleToken.Vault}) {
419 let blackHole = self.borrowRandomBlackHoleReceiver()
420 blackHole.deposit(from: <- vault)
421 }
422
423 /// ----- For BlackHole NFT Collection -----
424
425 /// Get the public path for the BlackHole Collection
426 ///
427 /// @return The PublicPath for the BlackHole Collection
428 ///
429 access(all)
430 view fun getBlackHoleCollectionPublicPath(): PublicPath {
431 return /public/BlackHoleNFTCollection
432 }
433
434 /// Get the storage path for the BlackHole Collection
435 ///
436 /// @return The StoragePath for the BlackHole Collection
437 ///
438 access(all)
439 view fun getBlackHoleCollectionStoragePath(): StoragePath {
440 return /storage/BlackHoleNFTCollection
441 }
442
443 /// Create a new BlackHole Resource
444 ///
445 access(all)
446 fun createNewBlackHoleCollection(): @Collection {
447 return <- create Collection()
448 }
449
450 /// Borrow a BlackHole Resource by the address
451 ///
452 access(all)
453 view fun borrowBlackHoleCollection(_ addr: Address): &Collection? {
454 return getAccount(addr)
455 .capabilities
456 .borrow<&Collection>(self.getBlackHoleCollectionPublicPath())
457 }
458
459 /// Check if is the address a valid BlackHole address
460 ///
461 access(all)
462 view fun hasValidBlackHoleCollection(_ addr: Address): Bool {
463 return self.borrowBlackHoleCollection(addr)?.isValid() == true
464 }
465
466 init() {
467 let identifier = "BlackHole_".concat(self.account.address.toString()).concat("_receiver")
468 self.storagePath = StoragePath(identifier: identifier)!
469
470 self.blackHoles = {}
471 }
472}
473