From c8be8a762b4abe60f0dfb3ad256bb2ac2dc2fa4c Mon Sep 17 00:00:00 2001 From: Austin Kline Date: Sun, 21 Jan 2024 14:42:18 -0800 Subject: [PATCH 1/4] add contract that handles safe destruction of resources --- cadence/contracts/Burner.cdc | 16 +++++ cadence/contracts/SafeDestroyTest.cdc | 25 ++++++++ .../burner/create_and_destroy_safe.cdc | 9 +++ .../burner/create_and_destroy_unsafe.cdc | 9 +++ flow.json | 22 ++++++- test/Burner_test.cdc | 63 +++++++++++++++++++ 6 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 cadence/contracts/Burner.cdc create mode 100644 cadence/contracts/SafeDestroyTest.cdc create mode 100644 cadence/transactions/burner/create_and_destroy_safe.cdc create mode 100644 cadence/transactions/burner/create_and_destroy_unsafe.cdc create mode 100644 test/Burner_test.cdc diff --git a/cadence/contracts/Burner.cdc b/cadence/contracts/Burner.cdc new file mode 100644 index 0000000..686ff6e --- /dev/null +++ b/cadence/contracts/Burner.cdc @@ -0,0 +1,16 @@ +pub contract Burner { + pub resource interface SafeDestroy { + pub fun callback() + } + + pub fun safeDestroy(_ r: @AnyResource) { + if r.isInstance(Type<@{SafeDestroy}>()) { + let s <- (r as! @{SafeDestroy}) + s.callback() + destroy s + return + } + + destroy r + } +} \ No newline at end of file diff --git a/cadence/contracts/SafeDestroyTest.cdc b/cadence/contracts/SafeDestroyTest.cdc new file mode 100644 index 0000000..59e466f --- /dev/null +++ b/cadence/contracts/SafeDestroyTest.cdc @@ -0,0 +1,25 @@ +import "Burner" + +pub contract SafeDestroyTest { + pub resource Safe: Burner.SafeDestroy { + pub let allowDestroy: Bool + + pub fun callback() { + assert(self.allowDestroy, message: "allowDestroy must be set to true") + } + + init(_ allowDestroy: Bool) { + self.allowDestroy = allowDestroy + } + } + + pub resource Unsafe {} + + pub fun createSafe(allowDestroy: Bool): @Safe { + return <- create Safe(allowDestroy) + } + + pub fun createUnsafe(): @Unsafe { + return <- create Unsafe() + } +} \ No newline at end of file diff --git a/cadence/transactions/burner/create_and_destroy_safe.cdc b/cadence/transactions/burner/create_and_destroy_safe.cdc new file mode 100644 index 0000000..98c6ca0 --- /dev/null +++ b/cadence/transactions/burner/create_and_destroy_safe.cdc @@ -0,0 +1,9 @@ +import "SafeDestroyTest" +import "Burner" + +transaction(allowDestroy: Bool) { + prepare(acct: AuthAccount) { + let r <- SafeDestroyTest.createSafe(allowDestroy: allowDestroy) + Burner.safeDestroy(<- r) + } +} diff --git a/cadence/transactions/burner/create_and_destroy_unsafe.cdc b/cadence/transactions/burner/create_and_destroy_unsafe.cdc new file mode 100644 index 0000000..44fa596 --- /dev/null +++ b/cadence/transactions/burner/create_and_destroy_unsafe.cdc @@ -0,0 +1,9 @@ +import "SafeDestroyTest" +import "Burner" + +transaction { + prepare(acct: AuthAccount) { + let r <- SafeDestroyTest.createUnsafe() + Burner.safeDestroy(<- r) + } +} \ No newline at end of file diff --git a/flow.json b/flow.json index bde501e..2d20e31 100644 --- a/flow.json +++ b/flow.json @@ -36,6 +36,18 @@ "testing": "0x0000000000000007" } }, + "Burner": { + "source": "./cadence/contracts/Burner.cdc", + "aliases": { + "testing": "0x0000000000000007" + } + }, + "SafeDestroyTest": { + "source": "./cadence/contracts/SafeDestroyTest.cdc", + "aliases": { + "testing": "0x0000000000000007" + } + }, "FungibleToken": { "source": "./cadence/contracts/FungibleToken.cdc", "aliases": { @@ -116,7 +128,9 @@ "ExampleNFT", "ExampleToken", "ScopedNFTProviders", - "ScopedFTProviders" + "ScopedFTProviders", + "Burner", + "SafeDestroyTest" ] }, "testnet": { @@ -125,7 +139,8 @@ "StringUtils", "AddressUtils", "ScopedNFTProviders", - "ScopedFTProviders" + "ScopedFTProviders", + "Burner" ] }, "mainnet": { @@ -134,7 +149,8 @@ "StringUtils", "AddressUtils", "ScopedNFTProviders", - "ScopedFTProviders" + "ScopedFTProviders", + "Burner" ] } } diff --git a/test/Burner_test.cdc b/test/Burner_test.cdc new file mode 100644 index 0000000..1563265 --- /dev/null +++ b/test/Burner_test.cdc @@ -0,0 +1,63 @@ +import Test +import BlockchainHelpers + +pub let BurnerAccount = Test.getAccount(0x0000000000000007) + +pub fun setup() { + Test.deployContract(name: "Burner", path: "../cadence/contracts/Burner.cdc", arguments: []) + Test.deployContract(name: "SafeDestroyTest", path: "../cadence/contracts/SafeDestroyTest.cdc", arguments: []) +} + +pub fun testSafeDestory_Allowed() { + let acct = Test.createAccount() + txExecutor( + "burner/create_and_destroy_safe.cdc", + [acct], + [true] + ) +} + +pub fun testSafeDestroy_NotAllowed() { + let acct = Test.createAccount() + + Test.expectFailure(fun() { + txExecutor( + "burner/create_and_destroy_safe.cdc", + [acct], + [false] + ) + }, errorMessageSubstring: "allowDestroy must be set to true") +} + +pub fun testUnsafeDestroy_Allowed() { + let acct = Test.createAccount() + txExecutor( + "burner/create_and_destroy_unsafe.cdc", + [acct], + [] + ) +} + +pub fun loadCode(_ fileName: String, _ baseDirectory: String): String { + return Test.readFile("../cadence/".concat(baseDirectory).concat("/").concat(fileName)) +} + +pub fun txExecutor(_ txName: String, _ signers: [Test.Account], _ arguments: [AnyStruct]): Test.TransactionResult { + let txCode = loadCode(txName, "transactions") + + let authorizers: [Address] = [] + for signer in signers { + authorizers.append(signer.address) + } + let tx = Test.Transaction( + code: txCode, + authorizers: authorizers, + signers: signers, + arguments: arguments, + ) + let txResult = Test.executeTransaction(tx) + if let err = txResult.error { + panic(err.message) + } + return txResult +} \ No newline at end of file From c23423ff1d9eaeb1da3ab5718cdcd9213eb5dad5 Mon Sep 17 00:00:00 2001 From: Austin Kline Date: Mon, 22 Jan 2024 08:11:37 -0800 Subject: [PATCH 2/4] rename callback method to prevent collission with other future interfaces --- cadence/contracts/Burner.cdc | 16 ++++++++++++++-- cadence/contracts/SafeDestroyTest.cdc | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/cadence/contracts/Burner.cdc b/cadence/contracts/Burner.cdc index 686ff6e..79e2e50 100644 --- a/cadence/contracts/Burner.cdc +++ b/cadence/contracts/Burner.cdc @@ -1,12 +1,24 @@ +// Burner is a contract that can facilitate the destruction of any resource on flow. pub contract Burner { + + // When Crescendo (Cadence 1.0) is released, custom destructors will be removed from the language. + // SafeDestroy is an interface meant to replace custom destructors, allowing anyone to add a callback + // method to ensure they do not destroy something which is not meant to be. + // + // NOTE: The only way to see benefit from this interface is to call the safeDestroyCallback method yourself, + // or to always use the safeDestroy method in this contract. Anyone who owns a resource can always elect **not** + // to destroy a resource this way pub resource interface SafeDestroy { - pub fun callback() + pub fun safeDestroyCallback() } + // safeDestroy is a global burn method which will destroy any resource it is given. + // If the provided resource implements the SafeDestroy interface, it will call the safeDestroyCallback + // method and then destroy afterwards. pub fun safeDestroy(_ r: @AnyResource) { if r.isInstance(Type<@{SafeDestroy}>()) { let s <- (r as! @{SafeDestroy}) - s.callback() + s.safeDestroyCallback() destroy s return } diff --git a/cadence/contracts/SafeDestroyTest.cdc b/cadence/contracts/SafeDestroyTest.cdc index 59e466f..42aa4d9 100644 --- a/cadence/contracts/SafeDestroyTest.cdc +++ b/cadence/contracts/SafeDestroyTest.cdc @@ -4,7 +4,7 @@ pub contract SafeDestroyTest { pub resource Safe: Burner.SafeDestroy { pub let allowDestroy: Bool - pub fun callback() { + pub fun safeDestroyCallback() { assert(self.allowDestroy, message: "allowDestroy must be set to true") } From 390a5dff1d564c49416f6f42a78e5c3a58912ef1 Mon Sep 17 00:00:00 2001 From: Austin Kline Date: Tue, 23 Jan 2024 11:52:45 -0800 Subject: [PATCH 3/4] renaming and handle array/dict types --- .../{SafeDestroyTest.cdc => BurnableTest.cdc} | 6 +- cadence/contracts/Burner.cdc | 87 +++++++++++++++---- .../burner/create_and_destroy_array.cdc | 9 ++ .../burner/create_and_destroy_dict.cdc | 29 +++++++ .../burner/create_and_destroy_safe.cdc | 6 +- .../burner/create_and_destroy_unsafe.cdc | 6 +- flow.json | 6 +- test/Burner_test.cdc | 24 ++++- 8 files changed, 144 insertions(+), 29 deletions(-) rename cadence/contracts/{SafeDestroyTest.cdc => BurnableTest.cdc} (79%) create mode 100644 cadence/transactions/burner/create_and_destroy_array.cdc create mode 100644 cadence/transactions/burner/create_and_destroy_dict.cdc diff --git a/cadence/contracts/SafeDestroyTest.cdc b/cadence/contracts/BurnableTest.cdc similarity index 79% rename from cadence/contracts/SafeDestroyTest.cdc rename to cadence/contracts/BurnableTest.cdc index 42aa4d9..77f20c8 100644 --- a/cadence/contracts/SafeDestroyTest.cdc +++ b/cadence/contracts/BurnableTest.cdc @@ -1,10 +1,10 @@ import "Burner" -pub contract SafeDestroyTest { - pub resource Safe: Burner.SafeDestroy { +pub contract BurnableTest { + pub resource Safe: Burner.Burnable { pub let allowDestroy: Bool - pub fun safeDestroyCallback() { + pub fun burnCallback() { assert(self.allowDestroy, message: "allowDestroy must be set to true") } diff --git a/cadence/contracts/Burner.cdc b/cadence/contracts/Burner.cdc index 79e2e50..fe468c2 100644 --- a/cadence/contracts/Burner.cdc +++ b/cadence/contracts/Burner.cdc @@ -1,28 +1,83 @@ // Burner is a contract that can facilitate the destruction of any resource on flow. pub contract Burner { - // When Crescendo (Cadence 1.0) is released, custom destructors will be removed from the language. - // SafeDestroy is an interface meant to replace custom destructors, allowing anyone to add a callback - // method to ensure they do not destroy something which is not meant to be. + // When Crescendo (Cadence 1.0) is released, custom destructors will be removed from cadece. + // Burnable is an interface meant to replace this lost feature, allowing anyone to add a callback + // method to ensure they do not destroy something which is not meant to be, or to add logic based on destruction + // such as tracking the supply of an NFT Collection // - // NOTE: The only way to see benefit from this interface is to call the safeDestroyCallback method yourself, - // or to always use the safeDestroy method in this contract. Anyone who owns a resource can always elect **not** + // NOTE: The only way to see benefit from this interface is to call the burnCallback method yourself, + // or to always use the burn method in this contract. Anyone who owns a resource can always elect **not** // to destroy a resource this way - pub resource interface SafeDestroy { - pub fun safeDestroyCallback() + pub resource interface Burnable { + pub fun burnCallback() } - // safeDestroy is a global burn method which will destroy any resource it is given. - // If the provided resource implements the SafeDestroy interface, it will call the safeDestroyCallback + // burn is a global method which will destroy any resource it is given. + // If the provided resource implements the Burnable interface, it will call the burnCallback // method and then destroy afterwards. - pub fun safeDestroy(_ r: @AnyResource) { - if r.isInstance(Type<@{SafeDestroy}>()) { - let s <- (r as! @{SafeDestroy}) - s.safeDestroyCallback() + pub fun burn(_ r: @AnyResource) { + if r.isInstance(Type<@{Burnable}>()) { + let s <- (r as! @{Burnable}) + s.burnCallback() destroy s - return + } else if r.isInstance(Type<@[AnyResource]>()) { + let arr <- (r as! @[AnyResource]) + while arr.length > 0 { + let item <- arr.removeFirst() + self.burn(<-item) + } + destroy arr + } else if r.isInstance(Type<@{String: AnyResource}>()) { + let d <- (r as! @{String: AnyResource}) + let keys = d.keys + while keys.length > 0 { + let item <- d.remove(key: keys.removeFirst()) + self.burn(<-item) + } + destroy d + } else if r.isInstance(Type<@{Number: AnyResource}>()) { + let d <- (r as! @{Number: AnyResource}) + let keys = d.keys + while keys.length > 0 { + let item <- d.remove(key: keys.removeFirst()) + self.burn(<-item) + } + destroy d + } else if r.isInstance(Type<@{Type: AnyResource}>()) { + let d <- (r as! @{Type: AnyResource}) + let keys = d.keys + while keys.length > 0 { + let item <- d.remove(key: keys.removeFirst()) + self.burn(<-item) + } + destroy d + } else if r.isInstance(Type<@{Address: AnyResource}>()) { + let d <- (r as! @{Address: AnyResource}) + let keys = d.keys + while keys.length > 0 { + let item <- d.remove(key: keys.removeFirst()) + self.burn(<-item) + } + destroy d + } else if r.isInstance(Type<@{Path: AnyResource}>()) { + let d <- (r as! @{Path: AnyResource}) + let keys = d.keys + while keys.length > 0 { + let item <- d.remove(key: keys.removeFirst()) + self.burn(<-item) + } + destroy d + } else if r.isInstance(Type<@{Character: AnyResource}>()) { + let d <- (r as! @{Character: AnyResource}) + let keys = d.keys + while keys.length > 0 { + let item <- d.remove(key: keys.removeFirst()) + self.burn(<-item) + } + destroy d + } else { + destroy r } - - destroy r } } \ No newline at end of file diff --git a/cadence/transactions/burner/create_and_destroy_array.cdc b/cadence/transactions/burner/create_and_destroy_array.cdc new file mode 100644 index 0000000..571d837 --- /dev/null +++ b/cadence/transactions/burner/create_and_destroy_array.cdc @@ -0,0 +1,9 @@ +import "BurnableTest" +import "Burner" + +transaction(allowDestroy: Bool) { + prepare(acct: AuthAccount) { + let r <- BurnableTest.createSafe(allowDestroy: allowDestroy) + Burner.burn(<- [<- r]) + } +} diff --git a/cadence/transactions/burner/create_and_destroy_dict.cdc b/cadence/transactions/burner/create_and_destroy_dict.cdc new file mode 100644 index 0000000..a24c548 --- /dev/null +++ b/cadence/transactions/burner/create_and_destroy_dict.cdc @@ -0,0 +1,29 @@ +import "BurnableTest" +import "Burner" + +transaction(allowDestroy: Bool, dictType: Type) { + prepare(acct: AuthAccount) { + let r <- BurnableTest.createSafe(allowDestroy: allowDestroy) + if dictType.isSubtype(of: Type()) { + let d: @{Number: AnyResource} <- {1: <-r} + Burner.burn(<-d) + } else if dictType.isSubtype(of: Type()) { + let d: @{String: AnyResource} <- {"a": <-r} + Burner.burn(<-d) + } else if dictType.isSubtype(of: Type()) { + let d: @{Path: AnyResource} <- {/public/foo: <-r} + Burner.burn(<-d) + } else if dictType.isSubtype(of: Type
()) { + let d: @{Address: AnyResource} <- {Address(0x1): <-r} + Burner.burn(<-d) + } else if dictType.isSubtype(of: Type()) { + let d: @{Character: AnyResource} <- {"c": <-r} + Burner.burn(<-d) + } else if dictType.isSubtype(of: Type()) { + let d: @{Type: AnyResource} <- {Type(): <-r} + Burner.burn(<-d) + } else { + panic("unsupported dict type") + } + } +} diff --git a/cadence/transactions/burner/create_and_destroy_safe.cdc b/cadence/transactions/burner/create_and_destroy_safe.cdc index 98c6ca0..5355ae9 100644 --- a/cadence/transactions/burner/create_and_destroy_safe.cdc +++ b/cadence/transactions/burner/create_and_destroy_safe.cdc @@ -1,9 +1,9 @@ -import "SafeDestroyTest" +import "BurnableTest" import "Burner" transaction(allowDestroy: Bool) { prepare(acct: AuthAccount) { - let r <- SafeDestroyTest.createSafe(allowDestroy: allowDestroy) - Burner.safeDestroy(<- r) + let r <- BurnableTest.createSafe(allowDestroy: allowDestroy) + Burner.burn(<- r) } } diff --git a/cadence/transactions/burner/create_and_destroy_unsafe.cdc b/cadence/transactions/burner/create_and_destroy_unsafe.cdc index 44fa596..032cd42 100644 --- a/cadence/transactions/burner/create_and_destroy_unsafe.cdc +++ b/cadence/transactions/burner/create_and_destroy_unsafe.cdc @@ -1,9 +1,9 @@ -import "SafeDestroyTest" +import "BurnableTest" import "Burner" transaction { prepare(acct: AuthAccount) { - let r <- SafeDestroyTest.createUnsafe() - Burner.safeDestroy(<- r) + let r <- BurnableTest.createUnsafe() + Burner.burn(<- r) } } \ No newline at end of file diff --git a/flow.json b/flow.json index 2d20e31..30b1371 100644 --- a/flow.json +++ b/flow.json @@ -42,8 +42,8 @@ "testing": "0x0000000000000007" } }, - "SafeDestroyTest": { - "source": "./cadence/contracts/SafeDestroyTest.cdc", + "BurnableTest": { + "source": "./cadence/contracts/BurnableTest.cdc", "aliases": { "testing": "0x0000000000000007" } @@ -130,7 +130,7 @@ "ScopedNFTProviders", "ScopedFTProviders", "Burner", - "SafeDestroyTest" + "BurnableTest" ] }, "testnet": { diff --git a/test/Burner_test.cdc b/test/Burner_test.cdc index 1563265..a9a5c40 100644 --- a/test/Burner_test.cdc +++ b/test/Burner_test.cdc @@ -5,7 +5,7 @@ pub let BurnerAccount = Test.getAccount(0x0000000000000007) pub fun setup() { Test.deployContract(name: "Burner", path: "../cadence/contracts/Burner.cdc", arguments: []) - Test.deployContract(name: "SafeDestroyTest", path: "../cadence/contracts/SafeDestroyTest.cdc", arguments: []) + Test.deployContract(name: "BurnableTest", path: "../cadence/contracts/BurnableTest.cdc", arguments: []) } pub fun testSafeDestory_Allowed() { @@ -38,6 +38,28 @@ pub fun testUnsafeDestroy_Allowed() { ) } +pub fun testDestroy_Dict() { + let acct = Test.createAccount() + + let types = [Type
(), Type(), Type(), Type(), Type(), Type()] + for type in types { + txExecutor( + "burner/create_and_destroy_dict.cdc", + [acct], + [true, type] + ) + } +} + +pub fun testDestroy_Array() { + let acct = Test.createAccount() + txExecutor( + "burner/create_and_destroy_array.cdc", + [acct], + [true] + ) +} + pub fun loadCode(_ fileName: String, _ baseDirectory: String): String { return Test.readFile("../cadence/".concat(baseDirectory).concat("/").concat(fileName)) } From 3f8ec3ce9301f9cecfabbffa871ae334b90a86e0 Mon Sep 17 00:00:00 2001 From: Austin Kline Date: Tue, 23 Jan 2024 14:53:56 -0800 Subject: [PATCH 4/4] address comments, add assertion to ensure callback happened --- cadence/contracts/BurnableTest.cdc | 7 +++ cadence/contracts/Burner.cdc | 60 ++++++++----------- .../burner/create_and_destroy_array.cdc | 8 +++ .../burner/create_and_destroy_dict.cdc | 20 ++++--- .../burner/create_and_destroy_safe.cdc | 8 +++ .../burner/create_and_destroy_unsafe.cdc | 3 + test/Burner_test.cdc | 15 +++++ 7 files changed, 80 insertions(+), 41 deletions(-) diff --git a/cadence/contracts/BurnableTest.cdc b/cadence/contracts/BurnableTest.cdc index 77f20c8..143eead 100644 --- a/cadence/contracts/BurnableTest.cdc +++ b/cadence/contracts/BurnableTest.cdc @@ -1,11 +1,14 @@ import "Burner" pub contract BurnableTest { + pub var totalBurned: UInt64 + pub resource Safe: Burner.Burnable { pub let allowDestroy: Bool pub fun burnCallback() { assert(self.allowDestroy, message: "allowDestroy must be set to true") + BurnableTest.totalBurned = BurnableTest.totalBurned + 1 } init(_ allowDestroy: Bool) { @@ -22,4 +25,8 @@ pub contract BurnableTest { pub fun createUnsafe(): @Unsafe { return <- create Unsafe() } + + init() { + self.totalBurned = 0 + } } \ No newline at end of file diff --git a/cadence/contracts/Burner.cdc b/cadence/contracts/Burner.cdc index fe468c2..a81f9bb 100644 --- a/cadence/contracts/Burner.cdc +++ b/cadence/contracts/Burner.cdc @@ -17,65 +17,57 @@ pub contract Burner { // If the provided resource implements the Burnable interface, it will call the burnCallback // method and then destroy afterwards. pub fun burn(_ r: @AnyResource) { - if r.isInstance(Type<@{Burnable}>()) { - let s <- (r as! @{Burnable}) + if let s <- r as? @{Burnable} { s.burnCallback() destroy s - } else if r.isInstance(Type<@[AnyResource]>()) { - let arr <- (r as! @[AnyResource]) + } else if let arr <- r as? @[AnyResource] { while arr.length > 0 { let item <- arr.removeFirst() self.burn(<-item) } destroy arr - } else if r.isInstance(Type<@{String: AnyResource}>()) { - let d <- (r as! @{String: AnyResource}) - let keys = d.keys + } else if let stringDict <- r as? @{String: AnyResource} { + let keys = stringDict.keys while keys.length > 0 { - let item <- d.remove(key: keys.removeFirst()) + let item <- stringDict.remove(key: keys.removeFirst())! self.burn(<-item) } - destroy d - } else if r.isInstance(Type<@{Number: AnyResource}>()) { - let d <- (r as! @{Number: AnyResource}) - let keys = d.keys + destroy stringDict + } else if let numDict <- r as? @{Number: AnyResource} { + let keys = numDict.keys while keys.length > 0 { - let item <- d.remove(key: keys.removeFirst()) + let item <- numDict.remove(key: keys.removeFirst())! self.burn(<-item) } - destroy d - } else if r.isInstance(Type<@{Type: AnyResource}>()) { - let d <- (r as! @{Type: AnyResource}) - let keys = d.keys + destroy numDict + } else if let typeDict <- r as? @{Type: AnyResource} { + let keys = typeDict.keys while keys.length > 0 { - let item <- d.remove(key: keys.removeFirst()) + let item <- typeDict.remove(key: keys.removeFirst())! self.burn(<-item) } - destroy d - } else if r.isInstance(Type<@{Address: AnyResource}>()) { - let d <- (r as! @{Address: AnyResource}) - let keys = d.keys + destroy typeDict + } else if let addressDict <- r as? @{Address: AnyResource} { + let keys = addressDict.keys while keys.length > 0 { - let item <- d.remove(key: keys.removeFirst()) + let item <- addressDict.remove(key: keys.removeFirst())! self.burn(<-item) } - destroy d - } else if r.isInstance(Type<@{Path: AnyResource}>()) { - let d <- (r as! @{Path: AnyResource}) - let keys = d.keys + destroy addressDict + } else if let pathDict <- r as? @{Path: AnyResource} { + let keys = pathDict.keys while keys.length > 0 { - let item <- d.remove(key: keys.removeFirst()) + let item <- pathDict.remove(key: keys.removeFirst())! self.burn(<-item) } - destroy d - } else if r.isInstance(Type<@{Character: AnyResource}>()) { - let d <- (r as! @{Character: AnyResource}) - let keys = d.keys + destroy pathDict + } else if let charDict <- r as? @{Character: AnyResource} { + let keys = charDict.keys while keys.length > 0 { - let item <- d.remove(key: keys.removeFirst()) + let item <- charDict.remove(key: keys.removeFirst())! self.burn(<-item) } - destroy d + destroy charDict } else { destroy r } diff --git a/cadence/transactions/burner/create_and_destroy_array.cdc b/cadence/transactions/burner/create_and_destroy_array.cdc index 571d837..62a32d7 100644 --- a/cadence/transactions/burner/create_and_destroy_array.cdc +++ b/cadence/transactions/burner/create_and_destroy_array.cdc @@ -3,7 +3,15 @@ import "Burner" transaction(allowDestroy: Bool) { prepare(acct: AuthAccount) { + let before = BurnableTest.totalBurned + let r <- BurnableTest.createSafe(allowDestroy: allowDestroy) Burner.burn(<- [<- r]) + + if allowDestroy { + assert(before + 1 == BurnableTest.totalBurned, message: "totalBurned was lower than expected") + } else { + assert(before == BurnableTest.totalBurned, message: "totalBurned value changed unexpectedly") + } } } diff --git a/cadence/transactions/burner/create_and_destroy_dict.cdc b/cadence/transactions/burner/create_and_destroy_dict.cdc index a24c548..0c92020 100644 --- a/cadence/transactions/burner/create_and_destroy_dict.cdc +++ b/cadence/transactions/burner/create_and_destroy_dict.cdc @@ -3,27 +3,33 @@ import "Burner" transaction(allowDestroy: Bool, dictType: Type) { prepare(acct: AuthAccount) { + let before = BurnableTest.totalBurned + let r <- BurnableTest.createSafe(allowDestroy: allowDestroy) - if dictType.isSubtype(of: Type()) { + if dictType as? Number != nil { let d: @{Number: AnyResource} <- {1: <-r} Burner.burn(<-d) - } else if dictType.isSubtype(of: Type()) { + } else if dictType as? String != nil { let d: @{String: AnyResource} <- {"a": <-r} Burner.burn(<-d) - } else if dictType.isSubtype(of: Type()) { + } else if dictType as? Path != nil { let d: @{Path: AnyResource} <- {/public/foo: <-r} Burner.burn(<-d) - } else if dictType.isSubtype(of: Type
()) { + } else if dictType as? Address != nil { let d: @{Address: AnyResource} <- {Address(0x1): <-r} Burner.burn(<-d) - } else if dictType.isSubtype(of: Type()) { + } else if dictType as? Character != nil { let d: @{Character: AnyResource} <- {"c": <-r} Burner.burn(<-d) - } else if dictType.isSubtype(of: Type()) { + } else { let d: @{Type: AnyResource} <- {Type(): <-r} Burner.burn(<-d) + } + + if allowDestroy { + assert(before + 1 == BurnableTest.totalBurned, message: "totalBurned was lower than expected") } else { - panic("unsupported dict type") + assert(before == BurnableTest.totalBurned, message: "totalBurned value changed unexpectedly") } } } diff --git a/cadence/transactions/burner/create_and_destroy_safe.cdc b/cadence/transactions/burner/create_and_destroy_safe.cdc index 5355ae9..5dd74e9 100644 --- a/cadence/transactions/burner/create_and_destroy_safe.cdc +++ b/cadence/transactions/burner/create_and_destroy_safe.cdc @@ -3,7 +3,15 @@ import "Burner" transaction(allowDestroy: Bool) { prepare(acct: AuthAccount) { + let before = BurnableTest.totalBurned + let r <- BurnableTest.createSafe(allowDestroy: allowDestroy) Burner.burn(<- r) + + if allowDestroy { + assert(before + 1 == BurnableTest.totalBurned, message: "totalBurned was lower than expected") + } else { + assert(before == BurnableTest.totalBurned, message: "totalBurned value changed unexpectedly") + } } } diff --git a/cadence/transactions/burner/create_and_destroy_unsafe.cdc b/cadence/transactions/burner/create_and_destroy_unsafe.cdc index 032cd42..4407369 100644 --- a/cadence/transactions/burner/create_and_destroy_unsafe.cdc +++ b/cadence/transactions/burner/create_and_destroy_unsafe.cdc @@ -3,7 +3,10 @@ import "Burner" transaction { prepare(acct: AuthAccount) { + let before = BurnableTest.totalBurned let r <- BurnableTest.createUnsafe() Burner.burn(<- r) + + assert(before == BurnableTest.totalBurned, message: "unexpected totalBurned value") } } \ No newline at end of file diff --git a/test/Burner_test.cdc b/test/Burner_test.cdc index a9a5c40..bded301 100644 --- a/test/Burner_test.cdc +++ b/test/Burner_test.cdc @@ -51,6 +51,21 @@ pub fun testDestroy_Dict() { } } +pub fun testDestroy_Dict_NotAllowed() { + let acct = Test.createAccount() + + let types = [Type
(), Type(), Type(), Type(), Type(), Type()] + for type in types { + Test.expectFailure(fun() { + txExecutor( + "burner/create_and_destroy_dict.cdc", + [acct], + [false, type] + ) + }, errorMessageSubstring: "allowDestroy must be set to true") + } +} + pub fun testDestroy_Array() { let acct = Test.createAccount() txExecutor(