From fb29f9473953f3a4536c439039de44f0d14e4d77 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:44:48 -0600 Subject: [PATCH 01/31] fix incorrect Burner Testnet alias --- flow.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flow.json b/flow.json index d345b271..f39cf1d0 100644 --- a/flow.json +++ b/flow.json @@ -255,7 +255,7 @@ "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "f233dcee88fe0abe", - "testnet": "8c5303eaa26202d6" + "testnet": "9a0766d93b6608b7" } }, "EVM": { From aea1fbb8889460d73bfbae28cf15c91360049937 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:45:13 -0600 Subject: [PATCH 02/31] fix field name typo --- cadence/contracts/bridge/FlowEVMBridgeConfig.cdc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc b/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc index 6a93446d..eb553b5b 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc @@ -278,28 +278,28 @@ contract FlowEVMBridgeConfig { access(all) resource EVMBlocklist { /// Mapping of serialized EVM addresses to their blocked status /// - access(all) let blockList: {String: Bool} + access(all) let blocklist: {String: Bool} init() { - self.blockList = {} + self.blocklist = {} } /// Returns whether the given EVM address is blocked from onboarding to the bridge /// access(all) view fun isBlocked(_ evmAddress: EVM.EVMAddress): Bool { - return self.blockList[evmAddress.toString()] ?? false + return self.blocklist[evmAddress.toString()] ?? false } /// Blocks the given EVM address from onboarding to the bridge /// access(Blocklist) fun block(_ evmAddress: EVM.EVMAddress) { - self.blockList[evmAddress.toString()] = true + self.blocklist[evmAddress.toString()] = true } /// Removes the given EVM address from the blocklist /// access(Blocklist) fun unblock(_ evmAddress: EVM.EVMAddress) { - self.blockList.remove(key: evmAddress.toString()) + self.blocklist.remove(key: evmAddress.toString()) } } From 77d0e6ed9394df9d9ba47bdfd5b7fdc0d875829f Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:55:28 -0600 Subject: [PATCH 03/31] remove FlowEVMBridgeConfig.initBlocklist() --- cadence/contracts/bridge/FlowEVMBridgeConfig.cdc | 15 +++------------ cadence/tests/flow_evm_bridge_tests.cdc | 7 ------- .../bridge/admin/blocklist/block_evm_address.cdc | 1 - .../bridge/admin/blocklist/init_blocklist.cdc | 14 -------------- 4 files changed, 3 insertions(+), 34 deletions(-) delete mode 100644 cadence/transactions/bridge/admin/blocklist/init_blocklist.cdc diff --git a/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc b/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc index eb553b5b..0d807951 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc @@ -229,18 +229,6 @@ contract FlowEVMBridgeConfig { ?? panic("Missing or mis-typed Blocklist in storage") } - /// Temporary method to initialize the EVMBlocklist resource as this resource was added after the contract was - /// deployed - /// - access(all) - fun initBlocklist() { - let path = /storage/evmBlocklist - if self.account.storage.type(at: path) != nil{ - return - } - self.account.storage.save(<-create EVMBlocklist(), to: path) - } - /***************** Constructs *****************/ @@ -508,5 +496,8 @@ contract FlowEVMBridgeConfig { self.account.storage.save(<-create Admin(), to: self.adminStoragePath) let adminCap = self.account.capabilities.storage.issue<&Admin>(self.adminStoragePath) self.account.capabilities.publish(adminCap, at: self.adminPublicPath) + + // Initialize the EVMBlocklist + self.account.storage.save(<-create EVMBlocklist(), to: /storage/evmBlocklist) } } diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index c2d27eec..12b61784 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -135,13 +135,6 @@ fun setup() { arguments: [] ) Test.expect(err, Test.beNil()) - // Initialize EVMBlocklist resource in account storage - let initBlocklistResult = executeTransaction( - "../transactions/bridge/admin/blocklist/init_blocklist.cdc", - [], - bridgeAccount - ) - Test.expect(initBlocklistResult, Test.beSucceeded()) // Deploy registry let registryDeploymentResult = executeTransaction( diff --git a/cadence/transactions/bridge/admin/blocklist/block_evm_address.cdc b/cadence/transactions/bridge/admin/blocklist/block_evm_address.cdc index 22a368df..a7607ea8 100644 --- a/cadence/transactions/bridge/admin/blocklist/block_evm_address.cdc +++ b/cadence/transactions/bridge/admin/blocklist/block_evm_address.cdc @@ -12,7 +12,6 @@ transaction(evmContractHex: String) { let evmAddress: EVM.EVMAddress prepare(signer: auth(BorrowValue) &Account) { - FlowEVMBridgeConfig.initBlocklist() self.evmBlocklist = signer.storage.borrow( from: /storage/evmBlocklist ) ?? panic("Could not borrow FlowEVMBridgeConfig Admin reference") diff --git a/cadence/transactions/bridge/admin/blocklist/init_blocklist.cdc b/cadence/transactions/bridge/admin/blocklist/init_blocklist.cdc deleted file mode 100644 index 4db39430..00000000 --- a/cadence/transactions/bridge/admin/blocklist/init_blocklist.cdc +++ /dev/null @@ -1,14 +0,0 @@ -import "EVM" - -import "FlowEVMBridgeConfig" - -/// Initializes the EVMBlocklist in the bridge account if it does not yet exist at the expected path -/// -transaction { - - prepare(signer: &Account) {} - - execute { - FlowEVMBridgeConfig.initBlocklist() - } -} From 0460f7f3587d1500e010bf216147c1080a626e84 Mon Sep 17 00:00:00 2001 From: Bjarte Stien Karlsen Date: Fri, 11 Oct 2024 20:12:04 +0200 Subject: [PATCH 04/31] added display-type to generated serialized json for attributes --- cadence/contracts/utils/SerializeMetadata.cdc | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/cadence/contracts/utils/SerializeMetadata.cdc b/cadence/contracts/utils/SerializeMetadata.cdc index 3cff96de..d90fed98 100644 --- a/cadence/contracts/utils/SerializeMetadata.cdc +++ b/cadence/contracts/utils/SerializeMetadata.cdc @@ -84,13 +84,13 @@ access(all) contract SerializeMetadata { // Append results from the token-level Display view to the serialized JSON compatible string if nftDisplay != nil { serializedResult = serializedResult - .concat(name).concat(Serialize.tryToJSONString(nftDisplay!.name)!).concat(", ") - .concat(description).concat(Serialize.tryToJSONString(nftDisplay!.description)!).concat(", ") - .concat(image).concat(Serialize.tryToJSONString(nftDisplay!.thumbnail.uri())!) + .concat(name).concat(Serialize.tryToJSONString(nftDisplay!.name)!).concat(", ") + .concat(description).concat(Serialize.tryToJSONString(nftDisplay!.description)!).concat(", ") + .concat(image).concat(Serialize.tryToJSONString(nftDisplay!.thumbnail.uri())!) // Append the `externa_url` value from NFTCollectionDisplay view if present if collectionDisplay != nil { return serializedResult.concat(", ") - .concat(externalURL).concat(Serialize.tryToJSONString(collectionDisplay!.externalURL.url)!) + .concat(externalURL).concat(Serialize.tryToJSONString(collectionDisplay!.externalURL.url)!) } } @@ -100,10 +100,10 @@ access(all) contract SerializeMetadata { // Without token-level view, serialize as contract-level metadata return serializedResult - .concat(name).concat(Serialize.tryToJSONString(collectionDisplay!.name)!).concat(", ") - .concat(description).concat(Serialize.tryToJSONString(collectionDisplay!.description)!).concat(", ") - .concat(image).concat(Serialize.tryToJSONString(collectionDisplay!.squareImage.file.uri())!).concat(", ") - .concat(externalLink).concat(Serialize.tryToJSONString(collectionDisplay!.externalURL.url)!) + .concat(name).concat(Serialize.tryToJSONString(collectionDisplay!.name)!).concat(", ") + .concat(description).concat(Serialize.tryToJSONString(collectionDisplay!.description)!).concat(", ") + .concat(image).concat(Serialize.tryToJSONString(collectionDisplay!.squareImage.file.uri())!).concat(", ") + .concat(externalLink).concat(Serialize.tryToJSONString(collectionDisplay!.externalURL.url)!) } /// Serializes given Traits view as a JSON compatible string. If a given Trait is not serializable, it is skipped @@ -129,9 +129,10 @@ access(all) contract SerializeMetadata { continue } serializedResult = serializedResult.concat("{") - .concat("\"trait_type\": ").concat(Serialize.tryToJSONString(trait.name)!) - .concat(", \"value\": ").concat(value!) - .concat("}") + .concat("\"trait_type\": ").concat(Serialize.tryToJSONString(trait.name)!) + .concat(", \"display_type\": ").concat(Serialize.tryToJSONString(trait.display_type)!) + .concat(", \"value\": ").concat(value!) + .concat("}") if i < traits!.traits.length - 1 { serializedResult = serializedResult.concat(",") } @@ -160,11 +161,11 @@ access(all) contract SerializeMetadata { let externalLink = "\"external_link\": " return "data:application/json;utf8,{" - .concat(name).concat(Serialize.tryToJSONString(ftDisplay.name)!).concat(", ") - .concat(symbol).concat(Serialize.tryToJSONString(ftDisplay.symbol)!).concat(", ") - .concat(description).concat(Serialize.tryToJSONString(ftDisplay.description)!).concat(", ") - .concat(externalLink).concat(Serialize.tryToJSONString(ftDisplay.externalURL.url)!) - .concat("}") + .concat(name).concat(Serialize.tryToJSONString(ftDisplay.name)!).concat(", ") + .concat(symbol).concat(Serialize.tryToJSONString(ftDisplay.symbol)!).concat(", ") + .concat(description).concat(Serialize.tryToJSONString(ftDisplay.description)!).concat(", ") + .concat(externalLink).concat(Serialize.tryToJSONString(ftDisplay.externalURL.url)!) + .concat("}") } /// Derives a symbol for use as an ERC20 or ERC721 symbol from a given string, presumably a Cadence contract name. From ebdcec4f4350dfdeebde09bb6a3eb2ddc733a9f0 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:08:32 -0600 Subject: [PATCH 05/31] fix cases of underflow in event of storage decrease --- cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc | 9 ++++++++- cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc b/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc index 4be73e3b..c517faf5 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc @@ -104,7 +104,14 @@ access(all) contract FlowEVMBridgeNFTEscrow { locker.deposit(token: <-nft) let postStorageSnapshot = self.account.storage.used - return postStorageSnapshot - preStorageSnapshot + // Return the amount of storage used by the locker after storing the NFT + if postStorageSnapshot < preStorageSnapshot { + // Due to atree inlining, account storage usage may counterintuitively decrease at times - return 0 + return 0 + } else { + // Otherwise, return the storage usage delta + return postStorageSnapshot - preStorageSnapshot + } } /// Unlocks the NFT of the given type and ID, reverting if it isn't in escrow diff --git a/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc b/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc index a407b279..6b91651b 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc @@ -97,7 +97,14 @@ access(all) contract FlowEVMBridgeTokenEscrow { locker.deposit(from: <-vault) let postStorageSnapshot = self.account.storage.used - return postStorageSnapshot - preStorageSnapshot + // Return the amount of storage used by the locker after storing the NFT + if postStorageSnapshot < preStorageSnapshot { + // Due to atree inlining, account storage usage may counterintuitively decrease at times - return 0 + return 0 + } else { + // Otherwise, return the storage usage delta + return postStorageSnapshot - preStorageSnapshot + } } /// Unlocks the tokens of the given type and amount, reverting if it isn't in escrow From 262cd43218aa237fcb97e1fe93c7e3d02caa1f1f Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Tue, 22 Oct 2024 16:25:38 +0800 Subject: [PATCH 06/31] feat: add a new transaction for creating new account with coa - Mainly from the usage of CEXs --- .../evm/create_new_account_with_coa.cdc | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 cadence/transactions/evm/create_new_account_with_coa.cdc diff --git a/cadence/transactions/evm/create_new_account_with_coa.cdc b/cadence/transactions/evm/create_new_account_with_coa.cdc new file mode 100644 index 00000000..512898bb --- /dev/null +++ b/cadence/transactions/evm/create_new_account_with_coa.cdc @@ -0,0 +1,64 @@ +import Crypto + +import "EVM" + +/// Creates a new Flow Address and EVM account with a Cadence Owned Account (COA) stored in the account's storage. +/// +transaction( + key: String, // key to be used for the account + signatureAlgorithm: UInt8, // signature algorithm to be used for the account + hashAlgorithm: UInt8, // hash algorithm to be used for the account + weight: UFix64, // weight to be used for the account +) { + let auth: auth(Storage, Keys, Capabilities) &Account + + prepare(signer: auth(Storage, Keys, Capabilities) &Account) { + pre { + signatureAlgorithm >= 1 && signatureAlgorithm <= 3: + "Cannot add Key: Must provide a signature algorithm raw value that corresponds to " + .concat("one of the available signature algorithms for Flow keys.") + .concat("You provided ").concat(signatureAlgorithm.toString()) + .concat(" but the options are either 1 (ECDSA_P256), 2 (ECDSA_secp256k1), or 3 (BLS_BLS12_381).") + hashAlgorithm >= 1 && hashAlgorithm <= 6: + "Cannot add Key: Must provide a hash algorithm raw value that corresponds to " + .concat("one of of the available hash algorithms for Flow keys.") + .concat("You provided ").concat(hashAlgorithm.toString()) + .concat(" but the options are 1 (SHA2_256), 2 (SHA2_384), 3 (SHA3_256), ") + .concat("4 (SHA3_384), 5 (KMAC128_BLS_BLS12_381), or 6 (KECCAK_256).") + weight <= 1000.0: + "Cannot add Key: The key weight must be between 0 and 1000." + .concat(" You provided ").concat(weight.toString()).concat(" which is invalid.") + } + + self.auth = signer + } + + execute { + // Create a new public key + let publicKey = PublicKey( + publicKey: key.decodeHex(), + signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! + ) + + // Create a new account + let account = Account(payer: self.auth) + + // Add the public key to the account + account.keys.add( + publicKey: publicKey, + hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, + weight: weight + ) + + // Create a new COA + let coa <- EVM.createCadenceOwnedAccount() + + // Save the COA to the new account + let storagePath = StoragePath(identifier: "evm")! + let publicPath = PublicPath(identifier: "evm")! + account.storage.save<@EVM.CadenceOwnedAccount>(<-coa, to: storagePath) + let addressableCap = account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(storagePath) + account.capabilities.unpublish(publicPath) + account.capabilities.publish(addressableCap, at: publicPath) + } +} From 3b438360276eaa9eee38d277970b4c5def991ff4 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Tue, 22 Oct 2024 16:32:24 +0800 Subject: [PATCH 07/31] chore: tab fix --- .../evm/create_new_account_with_coa.cdc | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cadence/transactions/evm/create_new_account_with_coa.cdc b/cadence/transactions/evm/create_new_account_with_coa.cdc index 512898bb..8127c349 100644 --- a/cadence/transactions/evm/create_new_account_with_coa.cdc +++ b/cadence/transactions/evm/create_new_account_with_coa.cdc @@ -13,38 +13,38 @@ transaction( let auth: auth(Storage, Keys, Capabilities) &Account prepare(signer: auth(Storage, Keys, Capabilities) &Account) { - pre { - signatureAlgorithm >= 1 && signatureAlgorithm <= 3: + pre { + signatureAlgorithm >= 1 && signatureAlgorithm <= 3: "Cannot add Key: Must provide a signature algorithm raw value that corresponds to " .concat("one of the available signature algorithms for Flow keys.") .concat("You provided ").concat(signatureAlgorithm.toString()) .concat(" but the options are either 1 (ECDSA_P256), 2 (ECDSA_secp256k1), or 3 (BLS_BLS12_381).") - hashAlgorithm >= 1 && hashAlgorithm <= 6: + hashAlgorithm >= 1 && hashAlgorithm <= 6: "Cannot add Key: Must provide a hash algorithm raw value that corresponds to " .concat("one of of the available hash algorithms for Flow keys.") .concat("You provided ").concat(hashAlgorithm.toString()) .concat(" but the options are 1 (SHA2_256), 2 (SHA2_384), 3 (SHA3_256), ") .concat("4 (SHA3_384), 5 (KMAC128_BLS_BLS12_381), or 6 (KECCAK_256).") - weight <= 1000.0: + weight <= 1000.0: "Cannot add Key: The key weight must be between 0 and 1000." .concat(" You provided ").concat(weight.toString()).concat(" which is invalid.") - } + } self.auth = signer } execute { // Create a new public key - let publicKey = PublicKey( - publicKey: key.decodeHex(), - signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! - ) + let publicKey = PublicKey( + publicKey: key.decodeHex(), + signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! + ) // Create a new account - let account = Account(payer: self.auth) + let account = Account(payer: self.auth) // Add the public key to the account - account.keys.add( + account.keys.add( publicKey: publicKey, hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, weight: weight From b4418881d7b707a761ad3a428173530dc76050e4 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:04:48 -0600 Subject: [PATCH 08/31] fix failing serialization test --- cadence/contracts/utils/SerializeMetadata.cdc | 2 +- cadence/tests/serialize_metadata_tests.cdc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/contracts/utils/SerializeMetadata.cdc b/cadence/contracts/utils/SerializeMetadata.cdc index d90fed98..3f3911df 100644 --- a/cadence/contracts/utils/SerializeMetadata.cdc +++ b/cadence/contracts/utils/SerializeMetadata.cdc @@ -130,7 +130,7 @@ access(all) contract SerializeMetadata { } serializedResult = serializedResult.concat("{") .concat("\"trait_type\": ").concat(Serialize.tryToJSONString(trait.name)!) - .concat(", \"display_type\": ").concat(Serialize.tryToJSONString(trait.display_type)!) + .concat(", \"display_type\": ").concat(Serialize.tryToJSONString(trait.displayType)!) .concat(", \"value\": ").concat(value!) .concat("}") if i < traits!.traits.length - 1 { diff --git a/cadence/tests/serialize_metadata_tests.cdc b/cadence/tests/serialize_metadata_tests.cdc index 40498d5c..0b5b6b98 100644 --- a/cadence/tests/serialize_metadata_tests.cdc +++ b/cadence/tests/serialize_metadata_tests.cdc @@ -57,8 +57,8 @@ fun testSerializeNFTSucceeds() { let heightString = mintedBlockHeight.toString() let expectedPrefix = "data:application/json;utf8,{\"name\": \"ExampleNFT\", \"description\": \"Example NFT Collection\", \"image\": \"https://flow.com/examplenft.jpg\", \"external_url\": \"https://example-nft.onflow.org\", " - let altSuffix1 = "\"attributes\": [{\"trait_type\": \"mintedBlock\", \"value\": \"".concat(heightString).concat("\"},{\"trait_type\": \"foo\", \"value\": \"nil\"}]}") - let altSuffix2 = "\"attributes\": [{\"trait_type\": \"foo\", \"value\": \"nil\"}]}, {\"trait_type\": \"mintedBlock\", \"value\": \"".concat(heightString).concat("\"}") + let altSuffix1 = "\"attributes\": [{\"trait_type\": \"mintedBlock\", \"display_type\": \"nil\", \"value\": \"".concat(heightString).concat("\"},{\"trait_type\": \"foo\", \"display_type\": \"nil\", \"value\": \"nil\"}]}") + let altSuffix2 = "\"attributes\": [{\"trait_type\": \"foo\", \"display_type\": \"nil\", \"value\": \"nil\"}]}, {\"trait_type\": \"mintedBlock\", \"display_type\": \"nil\", \"value\": \"".concat(heightString).concat("\"}") let idsResult = executeScript( "../scripts/nft/get_ids.cdc", From 6f57cf506a549f97f1779f0671324c1333d0dc72 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:58:01 -0600 Subject: [PATCH 09/31] update displayType trait serialization --- cadence/contracts/utils/SerializeMetadata.cdc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cadence/contracts/utils/SerializeMetadata.cdc b/cadence/contracts/utils/SerializeMetadata.cdc index 3f3911df..7a93695b 100644 --- a/cadence/contracts/utils/SerializeMetadata.cdc +++ b/cadence/contracts/utils/SerializeMetadata.cdc @@ -112,7 +112,7 @@ access(all) contract SerializeMetadata { /// @param traits: The Traits view to be serialized /// /// @returns: A JSON compatible string containing the serialized traits as: - /// `\"attributes\": [{\"trait_type\": \"\", \"value\": \"\"}, {...}]` + /// `\"attributes\": [{\"trait_type\": \"\", \"display_type\": \"\", \"value\": \"\"}, {...}]` /// access(all) fun serializeNFTTraitsAsAttributes(_ traits: MetadataViews.Traits): String { @@ -129,10 +129,13 @@ access(all) contract SerializeMetadata { continue } serializedResult = serializedResult.concat("{") - .concat("\"trait_type\": ").concat(Serialize.tryToJSONString(trait.name)!) - .concat(", \"display_type\": ").concat(Serialize.tryToJSONString(trait.displayType)!) - .concat(", \"value\": ").concat(value!) - .concat("}") + .concat("\"trait_type\": ").concat(Serialize.tryToJSONString(trait.name)!) + if trait.displayType != nil { + serializedResult = serializedResult.concat(", \"display_type\": ") + .concat(Serialize.tryToJSONString(trait.displayType)!) + } + serializedResult = serializedResult.concat(", \"value\": ").concat(value!) + .concat("}") if i < traits!.traits.length - 1 { serializedResult = serializedResult.concat(",") } From 4a11fd27b6892a1e1f8098152a1eca236136ef74 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:03:51 -0600 Subject: [PATCH 10/31] fix failing test case --- cadence/tests/serialize_metadata_tests.cdc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/tests/serialize_metadata_tests.cdc b/cadence/tests/serialize_metadata_tests.cdc index 0b5b6b98..40498d5c 100644 --- a/cadence/tests/serialize_metadata_tests.cdc +++ b/cadence/tests/serialize_metadata_tests.cdc @@ -57,8 +57,8 @@ fun testSerializeNFTSucceeds() { let heightString = mintedBlockHeight.toString() let expectedPrefix = "data:application/json;utf8,{\"name\": \"ExampleNFT\", \"description\": \"Example NFT Collection\", \"image\": \"https://flow.com/examplenft.jpg\", \"external_url\": \"https://example-nft.onflow.org\", " - let altSuffix1 = "\"attributes\": [{\"trait_type\": \"mintedBlock\", \"display_type\": \"nil\", \"value\": \"".concat(heightString).concat("\"},{\"trait_type\": \"foo\", \"display_type\": \"nil\", \"value\": \"nil\"}]}") - let altSuffix2 = "\"attributes\": [{\"trait_type\": \"foo\", \"display_type\": \"nil\", \"value\": \"nil\"}]}, {\"trait_type\": \"mintedBlock\", \"display_type\": \"nil\", \"value\": \"".concat(heightString).concat("\"}") + let altSuffix1 = "\"attributes\": [{\"trait_type\": \"mintedBlock\", \"value\": \"".concat(heightString).concat("\"},{\"trait_type\": \"foo\", \"value\": \"nil\"}]}") + let altSuffix2 = "\"attributes\": [{\"trait_type\": \"foo\", \"value\": \"nil\"}]}, {\"trait_type\": \"mintedBlock\", \"value\": \"".concat(heightString).concat("\"}") let idsResult = executeScript( "../scripts/nft/get_ids.cdc", From ea8630cac216ff63cecf0fd4d526cf9fbdb65981 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Wed, 23 Oct 2024 10:54:56 +0800 Subject: [PATCH 11/31] feat: hard code weight to 1000.0 and limit the algorithms --- .../evm/create_new_account_with_coa.cdc | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/cadence/transactions/evm/create_new_account_with_coa.cdc b/cadence/transactions/evm/create_new_account_with_coa.cdc index 8127c349..264e0b11 100644 --- a/cadence/transactions/evm/create_new_account_with_coa.cdc +++ b/cadence/transactions/evm/create_new_account_with_coa.cdc @@ -2,32 +2,28 @@ import Crypto import "EVM" -/// Creates a new Flow Address and EVM account with a Cadence Owned Account (COA) stored in the account's storage. +/// Creates a new Flow Address with a single full-weight key and its EVM account, which is +/// a Cadence Owned Account (COA) stored in the account's storage. /// transaction( key: String, // key to be used for the account signatureAlgorithm: UInt8, // signature algorithm to be used for the account hashAlgorithm: UInt8, // hash algorithm to be used for the account - weight: UFix64, // weight to be used for the account ) { let auth: auth(Storage, Keys, Capabilities) &Account prepare(signer: auth(Storage, Keys, Capabilities) &Account) { pre { - signatureAlgorithm >= 1 && signatureAlgorithm <= 3: + signatureAlgorithm == 1 || signatureAlgorithm == 2: "Cannot add Key: Must provide a signature algorithm raw value that corresponds to " .concat("one of the available signature algorithms for Flow keys.") .concat("You provided ").concat(signatureAlgorithm.toString()) - .concat(" but the options are either 1 (ECDSA_P256), 2 (ECDSA_secp256k1), or 3 (BLS_BLS12_381).") - hashAlgorithm >= 1 && hashAlgorithm <= 6: + .concat(" but the options are either 1 (ECDSA_P256), 2 (ECDSA_secp256k1).") + hashAlgorithm == 1 || hashAlgorithm == 3: "Cannot add Key: Must provide a hash algorithm raw value that corresponds to " .concat("one of of the available hash algorithms for Flow keys.") .concat("You provided ").concat(hashAlgorithm.toString()) - .concat(" but the options are 1 (SHA2_256), 2 (SHA2_384), 3 (SHA3_256), ") - .concat("4 (SHA3_384), 5 (KMAC128_BLS_BLS12_381), or 6 (KECCAK_256).") - weight <= 1000.0: - "Cannot add Key: The key weight must be between 0 and 1000." - .concat(" You provided ").concat(weight.toString()).concat(" which is invalid.") + .concat(" but the options are 1 (SHA2_256), 3 (SHA3_256), ") } self.auth = signer @@ -47,7 +43,7 @@ transaction( account.keys.add( publicKey: publicKey, hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, - weight: weight + weight: 1000.0 ) // Create a new COA From ce1239a14575faf5f3bd728b0dde057af7195f25 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Wed, 23 Oct 2024 10:57:26 +0800 Subject: [PATCH 12/31] chore: typo --- cadence/transactions/evm/create_new_account_with_coa.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/transactions/evm/create_new_account_with_coa.cdc b/cadence/transactions/evm/create_new_account_with_coa.cdc index 264e0b11..e7415138 100644 --- a/cadence/transactions/evm/create_new_account_with_coa.cdc +++ b/cadence/transactions/evm/create_new_account_with_coa.cdc @@ -23,7 +23,7 @@ transaction( "Cannot add Key: Must provide a hash algorithm raw value that corresponds to " .concat("one of of the available hash algorithms for Flow keys.") .concat("You provided ").concat(hashAlgorithm.toString()) - .concat(" but the options are 1 (SHA2_256), 3 (SHA3_256), ") + .concat(" but the options are 1 (SHA2_256), 3 (SHA3_256).") } self.auth = signer From 404a0ec6506f7c870bcb376139a586163505a46a Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:28:57 -0600 Subject: [PATCH 13/31] remove previewnet from flow.json --- flow.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/flow.json b/flow.json index f39cf1d0..65384829 100644 --- a/flow.json +++ b/flow.json @@ -264,7 +264,6 @@ "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "e467b9dd11fa00df", - "previewnet": "b6763b4399a888c8", "testnet": "8c5303eaa26202d6" } }, @@ -274,7 +273,6 @@ "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "e467b9dd11fa00df", - "previewnet": "b6763b4399a888c8", "testnet": "8c5303eaa26202d6" } }, @@ -284,7 +282,6 @@ "aliases": { "emulator": "0ae53cb6e3f42a79", "mainnet": "1654653399040a61", - "previewnet": "4445e7ad11568276", "testnet": "7e60df042a9c0868" } }, @@ -294,7 +291,6 @@ "aliases": { "emulator": "ee82856bf20e2aa6", "mainnet": "f233dcee88fe0abe", - "previewnet": "a0225e7000ac82a9", "testnet": "9a0766d93b6608b7" } }, @@ -304,7 +300,6 @@ "aliases": { "emulator": "ee82856bf20e2aa6", "mainnet": "f233dcee88fe0abe", - "previewnet": "a0225e7000ac82a9", "testnet": "9a0766d93b6608b7" } }, @@ -314,7 +309,6 @@ "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "1d7e57aa55817448", - "previewnet": "b6763b4399a888c8", "testnet": "631e88ae7f1d7c20" } }, @@ -324,7 +318,6 @@ "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "1d7e57aa55817448", - "previewnet": "b6763b4399a888c8", "testnet": "631e88ae7f1d7c20" } }, @@ -334,7 +327,6 @@ "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "1d7e57aa55817448", - "previewnet": "b6763b4399a888c8", "testnet": "631e88ae7f1d7c20" } } From 348012239adec69b42d78cc09b4c6a438074274b Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:30:14 -0600 Subject: [PATCH 14/31] optimize SerializeMetadata --- cadence/contracts/utils/SerializeMetadata.cdc | 66 ++++++++++--------- cadence/tests/serialize_metadata_tests.cdc | 1 - 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/cadence/contracts/utils/SerializeMetadata.cdc b/cadence/contracts/utils/SerializeMetadata.cdc index 7a93695b..3f0a4d8d 100644 --- a/cadence/contracts/utils/SerializeMetadata.cdc +++ b/cadence/contracts/utils/SerializeMetadata.cdc @@ -42,20 +42,16 @@ access(all) contract SerializeMetadata { return "" } // Init the data format prefix & concatenate the serialized display & attributes - var serializedMetadata = "data:application/json;utf8,{" + let parts: [String] = ["data:application/json;utf8,{"] if display != nil { - serializedMetadata = serializedMetadata.concat(display!) + parts.appendAll([display!, ", "]) // Include display if present & separate with a comma } - if display != nil && attributes != nil { - serializedMetadata = serializedMetadata.concat(", ") - } - if attributes != nil { - serializedMetadata = serializedMetadata.concat(attributes) - } - return serializedMetadata.concat("}") + parts.appendAll([attributes, "}"]) // Include attributes & close the JSON object + + return String.join(parts, separator: "") } - /// Serializes the display & collection display views of a given NFT as a JSON compatible string. If nftDisplay is + /// Serializes the display & collection display views of a given NFT as a JSON compatible string. If nftDisplay is /// present, the value is returned as token-level metadata. If nftDisplay is nil and collectionDisplay is present, /// the value is returned as contract-level metadata. If both values are nil, nil is returned. /// @@ -80,30 +76,34 @@ access(all) contract SerializeMetadata { let externalURL = "\"external_url\": " let externalLink = "\"external_link\": " var serializedResult = "" + let parts: [String] = [] // Append results from the token-level Display view to the serialized JSON compatible string if nftDisplay != nil { - serializedResult = serializedResult - .concat(name).concat(Serialize.tryToJSONString(nftDisplay!.name)!).concat(", ") - .concat(description).concat(Serialize.tryToJSONString(nftDisplay!.description)!).concat(", ") - .concat(image).concat(Serialize.tryToJSONString(nftDisplay!.thumbnail.uri())!) - // Append the `externa_url` value from NFTCollectionDisplay view if present + parts.appendAll([ + name, Serialize.tryToJSONString(nftDisplay!.name)!, ", ", + description, Serialize.tryToJSONString(nftDisplay!.description)!, ", ", + image, Serialize.tryToJSONString(nftDisplay!.thumbnail.uri())! + ]) + // Append the `external_url` value from NFTCollectionDisplay view if present if collectionDisplay != nil { - return serializedResult.concat(", ") - .concat(externalURL).concat(Serialize.tryToJSONString(collectionDisplay!.externalURL.url)!) + parts.appendAll([", ", externalURL, Serialize.tryToJSONString(collectionDisplay!.externalURL.url)!]) + return String.join(parts, separator: "") } } if collectionDisplay == nil { - return serializedResult + return String.join(parts, separator: "") } // Without token-level view, serialize as contract-level metadata - return serializedResult - .concat(name).concat(Serialize.tryToJSONString(collectionDisplay!.name)!).concat(", ") - .concat(description).concat(Serialize.tryToJSONString(collectionDisplay!.description)!).concat(", ") - .concat(image).concat(Serialize.tryToJSONString(collectionDisplay!.squareImage.file.uri())!).concat(", ") - .concat(externalLink).concat(Serialize.tryToJSONString(collectionDisplay!.externalURL.url)!) + parts.appendAll([ + name, Serialize.tryToJSONString(collectionDisplay!.name)!, ", ", + description, Serialize.tryToJSONString(collectionDisplay!.description)!, ", ", + image, Serialize.tryToJSONString(collectionDisplay!.squareImage.file.uri())!, ", ", + externalLink, Serialize.tryToJSONString(collectionDisplay!.externalURL.url)! + ]) + return String.join(parts, separator: "") } /// Serializes given Traits view as a JSON compatible string. If a given Trait is not serializable, it is skipped @@ -124,7 +124,7 @@ access(all) contract SerializeMetadata { if value == nil { // Remove trailing comma if last trait is not serializable if i == traitsLength - 1 && serializedResult[serializedResult.length - 1] == "," { - serializedResult = serializedResult.slice(from: 0, upTo: serializedResult.length - 1) + serializedResult = serializedResult.slice(from: 0, upTo: serializedResult.length - 1) } continue } @@ -143,10 +143,10 @@ access(all) contract SerializeMetadata { return serializedResult.concat("]") } - /// Serializes the FTDisplay view of a given fungible token as a JSON compatible data URL. The value is returned as + /// Serializes the FTDisplay view of a given fungible token as a JSON compatible data URL. The value is returned as /// contract-level metadata. /// - /// @param ftDisplay: The tokens's FTDisplay view from which values `name`, `symbol`, `description`, and + /// @param ftDisplay: The tokens's FTDisplay view from which values `name`, `symbol`, `description`, and /// `externaURL` are serialized /// /// @returns: A JSON compatible data URL string containing the serialized view as: @@ -162,13 +162,15 @@ access(all) contract SerializeMetadata { let symbol = "\"symbol\": " let description = "\"description\": " let externalLink = "\"external_link\": " + let parts: [String] = ["data:application/json;utf8,{"] - return "data:application/json;utf8,{" - .concat(name).concat(Serialize.tryToJSONString(ftDisplay.name)!).concat(", ") - .concat(symbol).concat(Serialize.tryToJSONString(ftDisplay.symbol)!).concat(", ") - .concat(description).concat(Serialize.tryToJSONString(ftDisplay.description)!).concat(", ") - .concat(externalLink).concat(Serialize.tryToJSONString(ftDisplay.externalURL.url)!) - .concat("}") + parts.appendAll([ + name, Serialize.tryToJSONString(ftDisplay.name)!, ", ", + symbol, Serialize.tryToJSONString(ftDisplay.symbol)!, ", ", + description, Serialize.tryToJSONString(ftDisplay.description)!, ", ", + externalLink, Serialize.tryToJSONString(ftDisplay.externalURL.url)! + ]) + return String.join(parts, separator: "") } /// Derives a symbol for use as an ERC20 or ERC721 symbol from a given string, presumably a Cadence contract name. diff --git a/cadence/tests/serialize_metadata_tests.cdc b/cadence/tests/serialize_metadata_tests.cdc index 40498d5c..16e0a9c5 100644 --- a/cadence/tests/serialize_metadata_tests.cdc +++ b/cadence/tests/serialize_metadata_tests.cdc @@ -74,7 +74,6 @@ fun testSerializeNFTSucceeds() { Test.expect(serializeMetadataResult, Test.beSucceeded()) let serializedMetadata = serializeMetadataResult.returnValue! as! String - Test.assertEqual(true, serializedMetadata == expectedPrefix.concat(altSuffix1) || serializedMetadata == expectedPrefix.concat(altSuffix2)) } From 7f552298b92dd67d1a3a06628e8d0f40e88aa88a Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:30:31 -0600 Subject: [PATCH 15/31] optimize Serialize --- cadence/contracts/utils/Serialize.cdc | 84 +++++++++++---------------- cadence/tests/serialize_tests.cdc | 1 + 2 files changed, 35 insertions(+), 50 deletions(-) diff --git a/cadence/contracts/utils/Serialize.cdc b/cadence/contracts/utils/Serialize.cdc index 0e2dc9ef..6c83f2ce 100644 --- a/cadence/contracts/utils/Serialize.cdc +++ b/cadence/contracts/utils/Serialize.cdc @@ -27,59 +27,59 @@ contract Serialize { case Type(): return "\"nil\"" case Type(): - return "\"".concat(value as! String).concat("\"") + return String.join(["\"", value as! String, "\"" ], separator: "") case Type(): - return "\"".concat(value as? String ?? "nil").concat("\"") + return String.join(["\"", value as? String ?? "nil", "\"" ], separator: "") case Type(): - return "\"".concat((value as! Character).toString()).concat("\"") + return String.join(["\"", (value as! Character).toString(), "\"" ], separator: "") case Type(): - return "\"".concat(value as! Bool ? "true" : "false").concat("\"") + return String.join(["\"", value as! Bool ? "true" : "false", "\"" ], separator: "") case Type
(): - return "\"".concat((value as! Address).toString()).concat("\"") + return String.join(["\"", (value as! Address).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as? Address)?.toString() ?? "nil").concat("\"") + return String.join(["\"", (value as? Address)?.toString() ?? "nil", "\"" ], separator: "") case Type(): - return "\"".concat((value as! Int8).toString()).concat("\"") + return String.join(["\"", (value as! Int8).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Int16).toString()).concat("\"") + return String.join(["\"", (value as! Int16).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Int32).toString()).concat("\"") + return String.join(["\"", (value as! Int32).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Int64).toString()).concat("\"") + return String.join(["\"", (value as! Int64).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Int128).toString()).concat("\"") + return String.join(["\"", (value as! Int128).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Int256).toString()).concat("\"") + return String.join(["\"", (value as! Int256).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Int).toString()).concat("\"") + return String.join(["\"", (value as! Int).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! UInt8).toString()).concat("\"") + return String.join(["\"", (value as! UInt8).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! UInt16).toString()).concat("\"") + return String.join(["\"", (value as! UInt16).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! UInt32).toString()).concat("\"") + return String.join(["\"", (value as! UInt32).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! UInt64).toString()).concat("\"") + return String.join(["\"", (value as! UInt64).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! UInt128).toString()).concat("\"") + return String.join(["\"", (value as! UInt128).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! UInt256).toString()).concat("\"") + return String.join(["\"", (value as! UInt256).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! UInt).toString()).concat("\"") + return String.join(["\"", (value as! UInt).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Word8).toString()).concat("\"") + return String.join(["\"", (value as! Word8).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Word16).toString()).concat("\"") + return String.join(["\"", (value as! Word16).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Word32).toString()).concat("\"") + return String.join(["\"", (value as! Word32).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Word64).toString()).concat("\"") + return String.join(["\"", (value as! Word64).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Word128).toString()).concat("\"") + return String.join(["\"", (value as! Word128).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! Word256).toString()).concat("\"") + return String.join(["\"", (value as! Word256).toString(), "\"" ], separator: "") case Type(): - return "\"".concat((value as! UFix64).toString()).concat("\"") + return String.join(["\"", (value as! UFix64).toString(), "\"" ], separator: "") default: return nil } @@ -89,24 +89,15 @@ contract Serialize { /// access(all) fun arrayToJSONString(_ arr: [AnyStruct]): String? { - var serializedArr = "[" - let arrLength = arr.length + let parts: [String]= [] for i, element in arr { let serializedElement = self.tryToJSONString(element) if serializedElement == nil { - if i == arrLength - 1 && serializedArr.length > 1 && serializedArr[serializedArr.length - 2] == "," { - // Remove trailing comma as this element could not be serialized - serializedArr = serializedArr.slice(from: 0, upTo: serializedArr.length - 2) - } continue } - serializedArr = serializedArr.concat(serializedElement!) - // Add a comma if there are more elements to serialize - if i < arr.length - 1 { - serializedArr = serializedArr.concat(", ") - } + parts.append(serializedElement!) } - return serializedArr.concat("]") + return "[".concat(String.join(parts, separator: ", ")).concat("]") } /// Returns a serialized representation of the given String-indexed mapping or nil if the value is not serializable. @@ -120,22 +111,15 @@ contract Serialize { dict.remove(key: k) } } - var serializedDict = "{" - let dictLength = dict.length + let parts: [String] = [] for i, key in dict.keys { let serializedValue = self.tryToJSONString(dict[key]!) if serializedValue == nil { - if i == dictLength - 1 && serializedDict.length > 1 && serializedDict[serializedDict.length - 2] == "," { - // Remove trailing comma as this element could not be serialized - serializedDict = serializedDict.slice(from: 0, upTo: serializedDict.length - 2) - } continue } - serializedDict = serializedDict.concat(self.tryToJSONString(key)!).concat(": ").concat(serializedValue!) - if i < dict.length - 1 { - serializedDict = serializedDict.concat(", ") - } + let serialializedKeyValue = String.join([self.tryToJSONString(key)!, serializedValue!], separator: ": ") + parts.append(serialializedKeyValue) } - return serializedDict.concat("}") + return "{".concat(String.join(parts, separator: ", ")).concat("}") } } diff --git a/cadence/tests/serialize_tests.cdc b/cadence/tests/serialize_tests.cdc index e472b907..b0c0b5ef 100644 --- a/cadence/tests/serialize_tests.cdc +++ b/cadence/tests/serialize_tests.cdc @@ -239,6 +239,7 @@ fun testDictToJSONStringSucceeds() { var expectedTwo: String = "{\"arr\": [\"127\", \"Hello, World!\"], \"bool\": \"true\"}" var actual: String? = Serialize.dictToJSONString(dict: dict, excludedNames: nil) + log(actual) Test.assertEqual(true, expectedOne == actual! || expectedTwo == actual!) actual = Serialize.tryToJSONString(dict) From 6f9f8624c07b526b1753754c68dd785547ef8443 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:50:03 -0600 Subject: [PATCH 16/31] fix serializeNFTMetadataAsURI conditional --- cadence/contracts/utils/SerializeMetadata.cdc | 5 +++-- cadence/tests/serialize_tests.cdc | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/contracts/utils/SerializeMetadata.cdc b/cadence/contracts/utils/SerializeMetadata.cdc index 3f0a4d8d..d38c4045 100644 --- a/cadence/contracts/utils/SerializeMetadata.cdc +++ b/cadence/contracts/utils/SerializeMetadata.cdc @@ -31,14 +31,15 @@ access(all) contract SerializeMetadata { // Serialize the display values from the NFT's Display & NFTCollectionDisplay views let nftDisplay = nft.resolveView(Type()) as! MetadataViews.Display? let collectionDisplay = nft.resolveView(Type()) as! MetadataViews.NFTCollectionDisplay? + // Serialize the display & collection display views - nil if both views are nil let display = self.serializeFromDisplays(nftDisplay: nftDisplay, collectionDisplay: collectionDisplay) // Get the Traits view from the NFT, returning early if no traits are found let traits = nft.resolveView(Type()) as! MetadataViews.Traits? let attributes = self.serializeNFTTraitsAsAttributes(traits ?? MetadataViews.Traits([])) - // Return an empty string if nothing is serializable - if display == nil && attributes == nil { + // Return an empty string if all views are nil + if display == nil && traits == nil { return "" } // Init the data format prefix & concatenate the serialized display & attributes diff --git a/cadence/tests/serialize_tests.cdc b/cadence/tests/serialize_tests.cdc index b0c0b5ef..e472b907 100644 --- a/cadence/tests/serialize_tests.cdc +++ b/cadence/tests/serialize_tests.cdc @@ -239,7 +239,6 @@ fun testDictToJSONStringSucceeds() { var expectedTwo: String = "{\"arr\": [\"127\", \"Hello, World!\"], \"bool\": \"true\"}" var actual: String? = Serialize.dictToJSONString(dict: dict, excludedNames: nil) - log(actual) Test.assertEqual(true, expectedOne == actual! || expectedTwo == actual!) actual = Serialize.tryToJSONString(dict) From 56cc52e85af6985debf85f2d07564f7e982525eb Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:18:00 -0600 Subject: [PATCH 17/31] optimize serializeNFTTraitsAsAttributes & add single trait serialization --- cadence/contracts/utils/Serialize.cdc | 4 +- cadence/contracts/utils/SerializeMetadata.cdc | 48 +++++++++++-------- cadence/tests/serialize_metadata_tests.cdc | 5 +- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/cadence/contracts/utils/Serialize.cdc b/cadence/contracts/utils/Serialize.cdc index 6c83f2ce..d8123728 100644 --- a/cadence/contracts/utils/Serialize.cdc +++ b/cadence/contracts/utils/Serialize.cdc @@ -90,7 +90,7 @@ contract Serialize { access(all) fun arrayToJSONString(_ arr: [AnyStruct]): String? { let parts: [String]= [] - for i, element in arr { + for element in arr { let serializedElement = self.tryToJSONString(element) if serializedElement == nil { continue @@ -112,7 +112,7 @@ contract Serialize { } } let parts: [String] = [] - for i, key in dict.keys { + for key in dict.keys { let serializedValue = self.tryToJSONString(dict[key]!) if serializedValue == nil { continue diff --git a/cadence/contracts/utils/SerializeMetadata.cdc b/cadence/contracts/utils/SerializeMetadata.cdc index d38c4045..0f299bec 100644 --- a/cadence/contracts/utils/SerializeMetadata.cdc +++ b/cadence/contracts/utils/SerializeMetadata.cdc @@ -112,36 +112,44 @@ access(all) contract SerializeMetadata { /// /// @param traits: The Traits view to be serialized /// - /// @returns: A JSON compatible string containing the serialized traits as: + /// @returns: A JSON compatible string containing the serialized traits as follows + /// (display_type omitted if trait.displayType == nil): /// `\"attributes\": [{\"trait_type\": \"\", \"display_type\": \"\", \"value\": \"\"}, {...}]` /// access(all) fun serializeNFTTraitsAsAttributes(_ traits: MetadataViews.Traits): String { // Serialize each trait as an attribute, building the serialized JSON compatible string - var serializedResult = "\"attributes\": [" + let parts: [String] = [] let traitsLength = traits.traits.length for i, trait in traits.traits { - let value = Serialize.tryToJSONString(trait.value) - if value == nil { - // Remove trailing comma if last trait is not serializable - if i == traitsLength - 1 && serializedResult[serializedResult.length - 1] == "," { - serializedResult = serializedResult.slice(from: 0, upTo: serializedResult.length - 1) - } + let attribute = self.serializeNFTTraitAsAttribute(trait) + if attribute == nil { continue } - serializedResult = serializedResult.concat("{") - .concat("\"trait_type\": ").concat(Serialize.tryToJSONString(trait.name)!) - if trait.displayType != nil { - serializedResult = serializedResult.concat(", \"display_type\": ") - .concat(Serialize.tryToJSONString(trait.displayType)!) - } - serializedResult = serializedResult.concat(", \"value\": ").concat(value!) - .concat("}") - if i < traits!.traits.length - 1 { - serializedResult = serializedResult.concat(",") - } + parts.append(attribute!) + } + // Join all serialized attributes with a comma separator, wrapping the result in square brackets under the + // `attributes` key + return "\"attributes\": [".concat(String.join(parts, separator: ", ")).concat("]") + } + + /// Serializes a given Trait as an attribute in a JSON compatible format. If the trait's value is not serializable, + /// nil is returned. + /// The format of the serialized trait is as follows (display_type omitted if trait.displayType == nil): + /// `{"trait_type": "", "display_type": "", "value": ""}` + access(all) + fun serializeNFTTraitAsAttribute(_ trait: MetadataViews.Trait): String? { + let value = Serialize.tryToJSONString(trait.value) + if value == nil { + return nil } - return serializedResult.concat("]") + let parts: [String] = ["{"] + parts.appendAll( [ "\"trait_type\": ", Serialize.tryToJSONString(trait.name)! ] ) + if trait.displayType != nil { + parts.appendAll( [ ", \"display_type\": ", Serialize.tryToJSONString(trait.displayType)! ] ) + } + parts.appendAll( [ ", \"value\": ", value! , "}" ] ) + return String.join(parts, separator: "") } /// Serializes the FTDisplay view of a given fungible token as a JSON compatible data URL. The value is returned as diff --git a/cadence/tests/serialize_metadata_tests.cdc b/cadence/tests/serialize_metadata_tests.cdc index 16e0a9c5..f35ddb23 100644 --- a/cadence/tests/serialize_metadata_tests.cdc +++ b/cadence/tests/serialize_metadata_tests.cdc @@ -56,9 +56,10 @@ fun testSerializeNFTSucceeds() { mintedBlockHeight = heightResult.returnValue! as! UInt64 let heightString = mintedBlockHeight.toString() + // Cadence dictionaries are not ordered by insertion order, so we need to check for both possible orderings let expectedPrefix = "data:application/json;utf8,{\"name\": \"ExampleNFT\", \"description\": \"Example NFT Collection\", \"image\": \"https://flow.com/examplenft.jpg\", \"external_url\": \"https://example-nft.onflow.org\", " - let altSuffix1 = "\"attributes\": [{\"trait_type\": \"mintedBlock\", \"value\": \"".concat(heightString).concat("\"},{\"trait_type\": \"foo\", \"value\": \"nil\"}]}") - let altSuffix2 = "\"attributes\": [{\"trait_type\": \"foo\", \"value\": \"nil\"}]}, {\"trait_type\": \"mintedBlock\", \"value\": \"".concat(heightString).concat("\"}") + let altSuffix1 = "\"attributes\": [{\"trait_type\": \"mintedBlock\", \"value\": \"".concat(heightString).concat("\"}, {\"trait_type\": \"foo\", \"value\": \"nil\"}]}") + let altSuffix2 = "\"attributes\": [{\"trait_type\": \"foo\", \"value\": \"nil\"}, {\"trait_type\": \"mintedBlock\", \"value\": \"".concat(heightString).concat("\"}]}") let idsResult = executeScript( "../scripts/nft/get_ids.cdc", From 7898bff3ac6bde05118c529abd4802cb9f034e8b Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:16:28 -0600 Subject: [PATCH 18/31] update attributions --- cadence/contracts/utils/Serialize.cdc | 2 +- cadence/contracts/utils/SerializeMetadata.cdc | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cadence/contracts/utils/Serialize.cdc b/cadence/contracts/utils/Serialize.cdc index d8123728..41c9892f 100644 --- a/cadence/contracts/utils/Serialize.cdc +++ b/cadence/contracts/utils/Serialize.cdc @@ -5,7 +5,7 @@ import "NonFungibleToken" /// This contract is a utility for serializing primitive types, arrays, and common metadata mapping formats to JSON /// compatible strings. Also included are interfaces enabling custom serialization for structs and resources. /// -/// Special thanks to @austinkline for the idea and initial implementation. +/// Special thanks to @austinkline for the idea and initial implementation & @bjartek + @bluesign for optimizations. /// access(all) contract Serialize { diff --git a/cadence/contracts/utils/SerializeMetadata.cdc b/cadence/contracts/utils/SerializeMetadata.cdc index 0f299bec..965bf0ba 100644 --- a/cadence/contracts/utils/SerializeMetadata.cdc +++ b/cadence/contracts/utils/SerializeMetadata.cdc @@ -8,6 +8,8 @@ import "Serialize" /// This contract defines methods for serializing NFT metadata as a JSON compatible string, according to the common /// OpenSea metadata format. NFTs and metadata views can be serialized by reference via contract methods. /// +/// Special thanks to @austinkline for the idea and initial implementation & @bjartek + @bluesign for optimizations. +/// access(all) contract SerializeMetadata { /// Serializes the metadata (as a JSON compatible String) for a given NFT according to formats expected by EVM From 653fb06bd2729bdeecfe3716e92314bcb36d3429 Mon Sep 17 00:00:00 2001 From: "BT.Wood(Tang Bo Hao)" Date: Thu, 24 Oct 2024 10:51:13 +0800 Subject: [PATCH 19/31] Update cadence/transactions/evm/create_new_account_with_coa.cdc Co-authored-by: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> --- cadence/transactions/evm/create_new_account_with_coa.cdc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/transactions/evm/create_new_account_with_coa.cdc b/cadence/transactions/evm/create_new_account_with_coa.cdc index e7415138..65f31685 100644 --- a/cadence/transactions/evm/create_new_account_with_coa.cdc +++ b/cadence/transactions/evm/create_new_account_with_coa.cdc @@ -10,9 +10,9 @@ transaction( signatureAlgorithm: UInt8, // signature algorithm to be used for the account hashAlgorithm: UInt8, // hash algorithm to be used for the account ) { - let auth: auth(Storage, Keys, Capabilities) &Account + let auth: auth(BorrowValue) &Account - prepare(signer: auth(Storage, Keys, Capabilities) &Account) { + prepare(signer: auth(BorrowValue) &Account) { pre { signatureAlgorithm == 1 || signatureAlgorithm == 2: "Cannot add Key: Must provide a signature algorithm raw value that corresponds to " From fae3977a20c438ff0323b7fc40a66be36eaf3393 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:08:57 -0600 Subject: [PATCH 20/31] remove unused value from SerializeMetadata --- cadence/contracts/utils/SerializeMetadata.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/utils/SerializeMetadata.cdc b/cadence/contracts/utils/SerializeMetadata.cdc index 965bf0ba..1ceca382 100644 --- a/cadence/contracts/utils/SerializeMetadata.cdc +++ b/cadence/contracts/utils/SerializeMetadata.cdc @@ -123,7 +123,7 @@ access(all) contract SerializeMetadata { // Serialize each trait as an attribute, building the serialized JSON compatible string let parts: [String] = [] let traitsLength = traits.traits.length - for i, trait in traits.traits { + for trait in traits.traits { let attribute = self.serializeNFTTraitAsAttribute(trait) if attribute == nil { continue From 47f8732115b01616fab8a98915764acc628b9d66 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:01:05 -0600 Subject: [PATCH 21/31] update bridge fee approximation in nft bridging txns --- cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc | 6 ++++-- .../bridge/nft/bridge_nft_to_any_cadence_address.cdc | 6 ++++-- .../bridge/nft/bridge_nft_to_any_evm_address.cdc | 9 ++++----- cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc | 9 ++++----- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc index 3e813a43..e569da73 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc @@ -64,8 +64,10 @@ transaction(nftIdentifier: String, id: UInt256) { /* --- Configure a ScopedFTProvider --- */ // - // Calculate the bridge fee - bridging from EVM consumes no storage, so flat fee - let approxFee = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0) + // Set a cap on the withdrawable bridge fee + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { let providerCap = signer.capabilities.storage.issue( diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc index 2c3f3aa3..6513cffb 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc @@ -67,8 +67,10 @@ transaction(nftIdentifier: String, id: UInt256, recipient: Address) { /* --- Configure a ScopedFTProvider --- */ // - // Calculate the bridge fee - bridging from EVM consumes no storage, so flat fee - let approxFee = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0) + // Set a cap on the withdrawable bridge fee + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { let providerCap = signer.capabilities.storage.issue( diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc index 2a33a33e..80182974 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc @@ -52,15 +52,14 @@ transaction(nftIdentifier: String, id: UInt64, recipient: String) { ) ?? panic("Could not access signer's NFT Collection") // Withdraw the requested NFT & calculate the approximate bridge fee based on NFT storage usage - let currentStorageUsage = signer.storage.used self.nft <- collection.withdraw(withdrawID: id) - let withdrawnStorageUsage = signer.storage.used var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: currentStorageUsage - withdrawnStorageUsage - ) * 1.10 + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nft.getType()) ?? panic("Bridge does not support this asset type") + // Add the onboarding fee if onboarding is necessary if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee } @@ -100,7 +99,7 @@ transaction(nftIdentifier: String, id: UInt64, recipient: String) { feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} ) } - // Execute the bridge transaction + // Execute the bridge to EVM let recipientEVMAddress = EVM.addressFromString(recipient) FlowEVMBridge.bridgeNFTToEVM( token: <-self.nft, diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc index 82986f80..9a987a3d 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc @@ -58,16 +58,15 @@ transaction(nftIdentifier: String, id: UInt64) { from: collectionData.storagePath ) ?? panic("Could not access signer's NFT Collection") - // Withdraw the requested NFT & calculate the approximate bridge fee based on NFT storage usage - let currentStorageUsage = signer.storage.used + // Withdraw the requested NFT & set a cap on the withdrawable bridge fee self.nft <- collection.withdraw(withdrawID: id) - let withdrawnStorageUsage = signer.storage.used var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: currentStorageUsage - withdrawnStorageUsage - ) * 1.10 + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nft.getType()) ?? panic("Bridge does not support this asset type") + // Add the onboarding fee if onboarding is necessary if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee } From 3df191453afa216e7501a783a9c980408189710c Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:01:22 -0600 Subject: [PATCH 22/31] add batch nft bridging to evm txns --- .../batch_bridge_nft_to_any_evm_address.cdc | 124 ++++++++++++++++++ .../bridge/nft/batch_bridge_nft_to_evm.cdc | 121 +++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc create mode 100644 cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc new file mode 100644 index 00000000..bc806731 --- /dev/null +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc @@ -0,0 +1,124 @@ +import "FungibleToken" +import "NonFungibleToken" +import "ViewResolver" +import "MetadataViews" +import "FlowToken" + +import "ScopedFTProviders" + +import "EVM" + +import "FlowEVMBridge" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" + +/// Bridges an NFT from the signer's collection in Cadence to the provided recipient in FlowEVM +/// +/// NOTE: This transaction also onboards the NFT to the bridge if necessary which may incur additional fees +/// than bridging an asset that has already been onboarded. +/// +/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier +/// @param id: The Cadence NFT.id of the NFT to bridge to EVM +/// @param recipient: The hex-encoded EVM address to receive the NFT +/// +transaction(nftIdentifier: String, ids: [UInt64], recipient: String) { + + let nftType: Type + let collection: auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection} + let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount + let requiresOnboarding: Bool + let scopedProvider: @ScopedFTProviders.ScopedFTProvider + + prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + /* --- Reference the signer's CadenceOwnedAccount --- */ + // + // Borrow a reference to the signer's COA + self.coa = signer.storage.borrow(from: /storage/evm) + ?? panic("Could not borrow COA from provided gateway address") + + /* --- Construct the NFT type --- */ + // + // Construct the NFT type from the provided identifier + self.nftType = CompositeType(nftIdentifier) + ?? panic("Could not construct NFT type from identifier: ".concat(nftIdentifier)) + // Parse the NFT identifier into its components + let nftContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.nftType) + ?? panic("Could not get contract address from identifier: ".concat(nftIdentifier)) + let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: self.nftType) + ?? panic("Could not get contract name from identifier: ".concat(nftIdentifier)) + + /* --- Retrieve the NFT --- */ + // + // Borrow a reference to the NFT collection, configuring if necessary + let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) + ?? panic("Could not borrow ViewResolver from NFT contract") + let collectionData = viewResolver.resolveContractView( + resourceType: self.nftType, + viewType: Type() + ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + self.collection = signer.storage.borrow( + from: collectionData.storagePath + ) ?? panic("Could not access signer's NFT Collection") + + // Withdraw the requested NFT & set a cap on the withdrawable bridge fee + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) + // Determine if the NFT requires onboarding - this impacts the fee required + self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) + ?? panic("Bridge does not support this asset type") + // Add the onboarding fee if onboarding is necessary + if self.requiresOnboarding { + approxFee = approxFee + FlowEVMBridgeConfig.onboardFee + } + + /* --- Configure a ScopedFTProvider --- */ + // + // Issue and store bridge-dedicated Provider Capability in storage if necessary + if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { + let providerCap = signer.capabilities.storage.issue( + /storage/flowTokenVault + ) + signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) + } + // Copy the stored Provider capability and create a ScopedFTProvider + let providerCapCopy = signer.storage.copy>( + from: FlowEVMBridgeConfig.providerCapabilityStoragePath + ) ?? panic("Invalid Provider Capability found in storage.") + let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) + self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( + provider: providerCapCopy, + filters: [ providerFilter ], + expiration: getCurrentBlock().timestamp + 1.0 + ) + } + + execute { + if self.requiresOnboarding { + // Onboard the NFT to the bridge + FlowEVMBridge.onboardByType( + self.nftType, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + } + + for id in ids { + // Withdraw the NFT & ensure it is the correct type + let nft <-self.collection.withdraw(withdrawID: id) + assert( + nft.getType() == self.nftType, + message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier).concat(", received: ").concat(nft.getType().identifier) + ) + // Execute the bridge to EVM + let recipientEVMAddress = EVM.addressFromString(recipient) + FlowEVMBridge.bridgeNFTToEVM( + token: <-nft, + to: EVM.addressFromString(recipient), + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + } + + // Destroy the ScopedFTProvider + destroy self.scopedProvider + } +} diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc new file mode 100644 index 00000000..ca321cc9 --- /dev/null +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc @@ -0,0 +1,121 @@ +import "FungibleToken" +import "NonFungibleToken" +import "ViewResolver" +import "MetadataViews" +import "FlowToken" + +import "ScopedFTProviders" + +import "EVM" + +import "FlowEVMBridge" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" + +/// Bridges NFTs (from the same collection) from the signer's collection in Cadence to the signer's COA in FlowEVM +/// +/// NOTE: This transaction also onboards the NFT to the bridge if necessary which may incur additional fees +/// than bridging an asset that has already been onboarded. +/// +/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier +/// @param id: The Cadence NFT.id of the NFT to bridge to EVM +/// +transaction(nftIdentifier: String, ids: [UInt64]) { + + let nftType: Type + let collection: auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection} + let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount + let requiresOnboarding: Bool + let scopedProvider: @ScopedFTProviders.ScopedFTProvider + + prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + /* --- Reference the signer's CadenceOwnedAccount --- */ + // + // Borrow a reference to the signer's COA + self.coa = signer.storage.borrow(from: /storage/evm) + ?? panic("Could not borrow COA from provided gateway address") + + /* --- Construct the NFT type --- */ + // + // Construct the NFT type from the provided identifier + self.nftType = CompositeType(nftIdentifier) + ?? panic("Could not construct NFT type from identifier: ".concat(nftIdentifier)) + // Parse the NFT identifier into its components + let nftContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.nftType) + ?? panic("Could not get contract address from identifier: ".concat(nftIdentifier)) + let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: self.nftType) + ?? panic("Could not get contract name from identifier: ".concat(nftIdentifier)) + + /* --- Retrieve the NFT --- */ + // + // Borrow a reference to the NFT collection, configuring if necessary + let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) + ?? panic("Could not borrow ViewResolver from NFT contract") + let collectionData = viewResolver.resolveContractView( + resourceType: self.nftType, + viewType: Type() + ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + self.collection = signer.storage.borrow( + from: collectionData.storagePath + ) ?? panic("Could not access signer's NFT Collection") + + // Withdraw the requested NFT & set a cap on the withdrawable bridge fee + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) + // Determine if the NFT requires onboarding - this impacts the fee required + self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) + ?? panic("Bridge does not support this asset type") + // Add the onboarding fee if onboarding is necessary + if self.requiresOnboarding { + approxFee = approxFee + FlowEVMBridgeConfig.onboardFee + } + + /* --- Configure a ScopedFTProvider --- */ + // + // Issue and store bridge-dedicated Provider Capability in storage if necessary + if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { + let providerCap = signer.capabilities.storage.issue( + /storage/flowTokenVault + ) + signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) + } + // Copy the stored Provider capability and create a ScopedFTProvider + let providerCapCopy = signer.storage.copy>( + from: FlowEVMBridgeConfig.providerCapabilityStoragePath + ) ?? panic("Invalid Provider Capability found in storage.") + let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) + self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( + provider: providerCapCopy, + filters: [ providerFilter ], + expiration: getCurrentBlock().timestamp + 1.0 + ) + } + + execute { + if self.requiresOnboarding { + // Onboard the NFT to the bridge + FlowEVMBridge.onboardByType( + self.nftType, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + } + + for id in ids { + // Withdraw the NFT & ensure it is the correct type + let nft <-self.collection.withdraw(withdrawID: id) + assert( + nft.getType() == self.nftType, + message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier).concat(", received: ").concat(nft.getType().identifier) + ) + // Execute the bridge to EVM for the current ID + self.coa.depositNFT( + nft: <-nft, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + } + + // Destroy the ScopedFTProvider + destroy self.scopedProvider + } +} From 2e871cb423b01a38e96a51441f964cf0d9c72b53 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:13:00 -0600 Subject: [PATCH 23/31] update token bridge fee approximation --- .../bridge/tokens/bridge_tokens_from_evm.cdc | 6 ++++-- .../tokens/bridge_tokens_to_any_cadence_address.cdc | 6 ++++-- .../bridge/tokens/bridge_tokens_to_any_evm_address.cdc | 9 +++------ .../transactions/bridge/tokens/bridge_tokens_to_evm.cdc | 9 +++------ 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc index 55d20a54..962f784f 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc @@ -74,8 +74,10 @@ transaction(vaultIdentifier: String, amount: UInt256) { /* --- Configure a ScopedFTProvider --- */ // - // Calculate the bridge fee - bridging from EVM consumes no storage, so flat fee - let approxFee = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0) + // Set a cap on the withdrawable bridge fee + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { let providerCap = signer.capabilities.storage.issue( diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc index ed576a31..46ac678a 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc @@ -77,8 +77,10 @@ transaction(vaultIdentifier: String, amount: UInt256, recipient: Address) { /* --- Configure a ScopedFTProvider --- */ // - // Calculate the bridge fee - bridging from EVM consumes no storage, so flat fee - let approxFee = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0) + // Set a cap on the withdrawable bridge fee + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { let providerCap = signer.capabilities.storage.issue( diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc index e967e8f5..0a4d12b2 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc @@ -54,14 +54,11 @@ transaction(vaultIdentifier: String, amount: UFix64, recipient: String) { from: vaultData.storagePath ) ?? panic("Could not access signer's FungibleToken Vault") - // Withdraw the requested balance & calculate the approximate bridge fee based on storage usage - let currentStorageUsage = signer.storage.used + // Withdraw the requested balance & set a cap on the withdrawable bridge fee self.sentVault <- vault.withdraw(amount: amount) - let withdrawnStorageUsage = signer.storage.used - // Approximate the bridge fee based on the difference in storage usage with some buffer var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: currentStorageUsage - withdrawnStorageUsage - ) * 1.10 + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) // Determine if the Vault requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.sentVault.getType()) ?? panic("Bridge does not support this asset type") diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc index 6fc0dd6d..fdc4e9ae 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc @@ -58,14 +58,11 @@ transaction(vaultIdentifier: String, amount: UFix64) { from: vaultData.storagePath ) ?? panic("Could not access signer's FungibleToken Vault") - // Withdraw the requested balance & calculate the approximate bridge fee based on storage usage - let currentStorageUsage = signer.storage.used + // Withdraw the requested balance & set a cap on the withdrawable bridge fee self.sentVault <- vault.withdraw(amount: amount) - let withdrawnStorageUsage = signer.storage.used - // Approximate the bridge fee based on the difference in storage usage with some buffer var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: currentStorageUsage - withdrawnStorageUsage - ) * 1.10 + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) // Determine if the Vault requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.sentVault.getType()) ?? panic("Bridge does not support this asset type") From 88140539d420cc939fc7e092b863d5380fc4d5bc Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:23:50 -0600 Subject: [PATCH 24/31] update transaction comments --- .../bridge/nft/batch_bridge_nft_to_any_evm_address.cdc | 4 +++- cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc index bc806731..eadda816 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc @@ -102,12 +102,14 @@ transaction(nftIdentifier: String, ids: [UInt64], recipient: String) { ) } + // Iterate over requested IDs and bridge each NFT to the provided recipient in EVM for id in ids { // Withdraw the NFT & ensure it is the correct type let nft <-self.collection.withdraw(withdrawID: id) assert( nft.getType() == self.nftType, - message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier).concat(", received: ").concat(nft.getType().identifier) + message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier) + .concat(", received: ").concat(nft.getType().identifier) ) // Execute the bridge to EVM let recipientEVMAddress = EVM.addressFromString(recipient) diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc index ca321cc9..a56ae2f5 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc @@ -101,12 +101,14 @@ transaction(nftIdentifier: String, ids: [UInt64]) { ) } + // Iterate over requested IDs and bridge each NFT to the signer's COA in EVM for id in ids { - // Withdraw the NFT & ensure it is the correct type + // Withdraw the NFT & ensure it's the correct type let nft <-self.collection.withdraw(withdrawID: id) assert( nft.getType() == self.nftType, - message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier).concat(", received: ").concat(nft.getType().identifier) + message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier) + .concat(", received: ").concat(nft.getType().identifier) ) // Execute the bridge to EVM for the current ID self.coa.depositNFT( From e122272d5984b6547d5e9a61cd6d023fde552ed9 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:59:24 -0600 Subject: [PATCH 25/31] add batch nft bridging from evm --- .../bridge/nft/batch_bridge_nft_from_evm.cdc | 111 +++++++++++++++++ ...atch_bridge_nft_to_any_cadence_address.cdc | 114 ++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc create mode 100644 cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc new file mode 100644 index 00000000..f8796423 --- /dev/null +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc @@ -0,0 +1,111 @@ +import "FungibleToken" +import "NonFungibleToken" +import "ViewResolver" +import "MetadataViews" +import "FlowToken" + +import "ScopedFTProviders" + +import "EVM" + +import "FlowEVMBridge" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" + +/// This transaction bridges NFTs from EVM to Cadence assuming the NFT has already been onboarded to the FlowEVMBridge +/// NOTE: The ERC721 must have first been onboarded to the bridge. This can be checked via the method +/// FlowEVMBridge.evmAddressRequiresOnboarding(address: self.evmContractAddress) +/// +/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier +/// @param ids: The ERC721 ids of the NFTs to bridge to Cadence from EVM +/// +transaction(nftIdentifier: String, ids: [UInt256]) { + + let nftType: Type + let collection: &{NonFungibleToken.Collection} + let scopedProvider: @ScopedFTProviders.ScopedFTProvider + let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount + + prepare(signer: auth(BorrowValue, CopyValue, IssueStorageCapabilityController, PublishCapability, SaveValue, UnpublishCapability) &Account) { + /* --- Reference the signer's CadenceOwnedAccount --- */ + // + // Borrow a reference to the signer's COA + self.coa = signer.storage.borrow(from: /storage/evm) + ?? panic("Could not borrow COA from provided gateway address") + + /* --- Construct the NFT type --- */ + // + // Construct the NFT type from the provided identifier + self.nftType = CompositeType(nftIdentifier) + ?? panic("Could not construct NFT type from identifier: ".concat(nftIdentifier)) + // Parse the NFT identifier into its components + let nftContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.nftType) + ?? panic("Could not get contract address from identifier: ".concat(nftIdentifier)) + let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: self.nftType) + ?? panic("Could not get contract name from identifier: ".concat(nftIdentifier)) + + /* --- Reference the signer's NFT Collection --- */ + // + // Borrow a reference to the NFT collection, configuring if necessary + let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) + ?? panic("Could not borrow ViewResolver from NFT contract") + let collectionData = viewResolver.resolveContractView( + resourceType: self.nftType, + viewType: Type() + ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + if signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) == nil { + signer.storage.save(<-collectionData.createEmptyCollection(), to: collectionData.storagePath) + signer.capabilities.unpublish(collectionData.publicPath) + let collectionCap = signer.capabilities.storage.issue<&{NonFungibleToken.Collection}>(collectionData.storagePath) + signer.capabilities.publish(collectionCap, at: collectionData.publicPath) + } + self.collection = signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) + ?? panic("Could not borrow collection from storage path") + + /* --- Configure a ScopedFTProvider --- */ + // + // Set a cap on the withdrawable bridge fee + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) + // Issue and store bridge-dedicated Provider Capability in storage if necessary + if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { + let providerCap = signer.capabilities.storage.issue( + /storage/flowTokenVault + ) + signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) + } + // Copy the stored Provider capability and create a ScopedFTProvider + let providerCapCopy = signer.storage.copy>( + from: FlowEVMBridgeConfig.providerCapabilityStoragePath + ) ?? panic("Invalid Provider Capability found in storage.") + let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) + self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( + provider: providerCapCopy, + filters: [ providerFilter ], + expiration: getCurrentBlock().timestamp + 1.0 + ) + } + + execute { + // Iterate over the provided ids + for id in ids { + // Execute the bridge + let nft: @{NonFungibleToken.NFT} <- self.coa.withdrawNFT( + type: self.nftType, + id: id, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + // Ensure the bridged nft is the correct type + assert( + nft.getType() == self.nftType, + message: "Bridged nft type mismatch - requeswted: ".concat(self.nftType.identifier) + .concat(", received: ").concat(nft.getType().identifier) + ) + // Deposit the bridged NFT into the signer's collection + self.collection.deposit(token: <-nft) + } + // Destroy the ScopedFTProvider + destroy self.scopedProvider + } +} diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc new file mode 100644 index 00000000..c0f40793 --- /dev/null +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc @@ -0,0 +1,114 @@ +import "FungibleToken" +import "NonFungibleToken" +import "ViewResolver" +import "MetadataViews" + +import "ScopedFTProviders" + +import "EVM" + +import "FlowEVMBridge" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" + +/// This transaction bridges NFTs from EVM to Cadence assuming the NFT has already been onboarded to the FlowEVMBridge. +/// Also know that the recipient Flow account must have a Receiver capable of receiving the this bridged NFT accessible +/// via published Capability at the token's standard path. +/// NOTE: The ERC721 must have first been onboarded to the bridge. This can be checked via the method +/// FlowEVMBridge.evmAddressRequiresOnboarding(address: self.evmContractAddress) +/// +/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier +/// @param ids: The ERC721 ids of the NFTs to bridge to Cadence from EVM +/// @param recipient: The Flow account address to receive the bridged NFT +/// +transaction(nftIdentifier: String, ids: [UInt256], recipient: Address) { + + let nftType: Type + let receiver: &{NonFungibleToken.Receiver} + let scopedProvider: @ScopedFTProviders.ScopedFTProvider + let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount + + prepare(signer: auth(BorrowValue, CopyValue, IssueStorageCapabilityController, PublishCapability, SaveValue, UnpublishCapability) &Account) { + /* --- Reference the signer's CadenceOwnedAccount --- */ + // + // Borrow a reference to the signer's COA + self.coa = signer.storage.borrow(from: /storage/evm) + ?? panic("Could not borrow COA from provided gateway address") + + /* --- Construct the NFT type --- */ + // + // Construct the NFT type from the provided identifier + self.nftType = CompositeType(nftIdentifier) + ?? panic("Could not construct NFT type from identifier: ".concat(nftIdentifier)) + // Parse the NFT identifier into its components + let nftContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.nftType) + ?? panic("Could not get contract address from identifier: ".concat(nftIdentifier)) + let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: self.nftType) + ?? panic("Could not get contract name from identifier: ".concat(nftIdentifier)) + + /* --- Reference the recipient's NFT Receiver --- */ + // + // Borrow a reference to the NFT collection, configuring if necessary + let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) + ?? panic("Could not borrow ViewResolver from NFT contract") + let collectionData = viewResolver.resolveContractView( + resourceType: self.nftType, + viewType: Type() + ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + // Configure the signer's account for this NFT + if signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) == nil { + signer.storage.save(<-collectionData.createEmptyCollection(), to: collectionData.storagePath) + signer.capabilities.unpublish(collectionData.publicPath) + let collectionCap = signer.capabilities.storage.issue<&{NonFungibleToken.Collection}>(collectionData.storagePath) + signer.capabilities.publish(collectionCap, at: collectionData.publicPath) + } + self.receiver = getAccount(recipient).capabilities.borrow<&{NonFungibleToken.Receiver}>(collectionData.publicPath) + ?? panic("Could not borrow Receiver from recipient's public capability path") + + /* --- Configure a ScopedFTProvider --- */ + // + // Set a cap on the withdrawable bridge fee + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + ) + // Issue and store bridge-dedicated Provider Capability in storage if necessary + if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { + let providerCap = signer.capabilities.storage.issue( + /storage/flowTokenVault + ) + signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) + } + // Copy the stored Provider capability and create a ScopedFTProvider + let providerCapCopy = signer.storage.copy>( + from: FlowEVMBridgeConfig.providerCapabilityStoragePath + ) ?? panic("Invalid Provider Capability found in storage.") + let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) + self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( + provider: providerCapCopy, + filters: [ providerFilter ], + expiration: getCurrentBlock().timestamp + 1.0 + ) + } + + execute { + // Iterate over the provided ids + for id in ids { + // Execute the bridge + let nft: @{NonFungibleToken.NFT} <- self.coa.withdrawNFT( + type: self.nftType, + id: id, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + // Ensure the bridged nft is the correct type + assert( + nft.getType() == self.nftType, + message: "Bridged nft type mismatch - requeswted: ".concat(self.nftType.identifier) + .concat(", received: ").concat(nft.getType().identifier) + ) + // Deposit the bridged NFT into the signer's collection + self.receiver.deposit(token: <-nft) + } + // Destroy the ScopedFTProvider + destroy self.scopedProvider + } +} From 8c3c54e50cc6646832777aa9c5d8445249b01cce Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:47:49 -0600 Subject: [PATCH 26/31] fix batch bridge fee calculation, adding base fee for each NFT --- cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc | 2 +- .../bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc | 2 +- .../bridge/nft/batch_bridge_nft_to_any_evm_address.cdc | 2 +- cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc index f8796423..f6705b18 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc @@ -67,7 +67,7 @@ transaction(nftIdentifier: String, ids: [UInt256]) { // Set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction - ) + ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { let providerCap = signer.capabilities.storage.issue( diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc index c0f40793..8e52a6a8 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc @@ -70,7 +70,7 @@ transaction(nftIdentifier: String, ids: [UInt256], recipient: Address) { // Set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction - ) + ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { let providerCap = signer.capabilities.storage.issue( diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc index eadda816..92afd18a 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc @@ -63,7 +63,7 @@ transaction(nftIdentifier: String, ids: [UInt64], recipient: String) { // Withdraw the requested NFT & set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction - ) + ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) ?? panic("Bridge does not support this asset type") diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc index a56ae2f5..4224965e 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc @@ -62,7 +62,7 @@ transaction(nftIdentifier: String, ids: [UInt64]) { // Withdraw the requested NFT & set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction - ) + ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) ?? panic("Bridge does not support this asset type") @@ -70,6 +70,7 @@ transaction(nftIdentifier: String, ids: [UInt64]) { if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee } + log(approxFee) /* --- Configure a ScopedFTProvider --- */ // From 9c6c01f20978d4d12497b53ede5a23e5e723d493 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:51:52 -0600 Subject: [PATCH 27/31] bump bridge storage fee calculation estimate to 400kB per txn --- cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc | 2 +- .../bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc | 2 +- .../bridge/nft/batch_bridge_nft_to_any_evm_address.cdc | 2 +- cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc | 3 +-- cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc | 2 +- .../bridge/nft/bridge_nft_to_any_cadence_address.cdc | 2 +- .../transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc | 2 +- cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc | 2 +- cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc | 2 +- .../bridge/tokens/bridge_tokens_to_any_cadence_address.cdc | 2 +- .../bridge/tokens/bridge_tokens_to_any_evm_address.cdc | 2 +- cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc | 2 +- 12 files changed, 12 insertions(+), 13 deletions(-) diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc index f6705b18..36ea1574 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc @@ -66,7 +66,7 @@ transaction(nftIdentifier: String, ids: [UInt256]) { // // Set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc index 8e52a6a8..686d26bb 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc @@ -69,7 +69,7 @@ transaction(nftIdentifier: String, ids: [UInt256], recipient: Address) { // // Set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc index 92afd18a..0a2b9995 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc @@ -62,7 +62,7 @@ transaction(nftIdentifier: String, ids: [UInt64], recipient: String) { // Withdraw the requested NFT & set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc index 4224965e..2997e855 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc @@ -61,7 +61,7 @@ transaction(nftIdentifier: String, ids: [UInt64]) { // Withdraw the requested NFT & set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) @@ -70,7 +70,6 @@ transaction(nftIdentifier: String, ids: [UInt64]) { if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee } - log(approxFee) /* --- Configure a ScopedFTProvider --- */ // diff --git a/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc index e569da73..8c2b9907 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc @@ -66,7 +66,7 @@ transaction(nftIdentifier: String, id: UInt256) { // // Set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc index 6513cffb..f90bed20 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc @@ -69,7 +69,7 @@ transaction(nftIdentifier: String, id: UInt256, recipient: Address) { // // Set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc index 80182974..221be214 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc @@ -54,7 +54,7 @@ transaction(nftIdentifier: String, id: UInt64, recipient: String) { // Withdraw the requested NFT & calculate the approximate bridge fee based on NFT storage usage self.nft <- collection.withdraw(withdrawID: id) var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nft.getType()) diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc index 9a987a3d..449ef397 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc @@ -61,7 +61,7 @@ transaction(nftIdentifier: String, id: UInt64) { // Withdraw the requested NFT & set a cap on the withdrawable bridge fee self.nft <- collection.withdraw(withdrawID: id) var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nft.getType()) diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc index 962f784f..c2bb1f92 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc @@ -76,7 +76,7 @@ transaction(vaultIdentifier: String, amount: UInt256) { // // Set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc index 46ac678a..54d076eb 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc @@ -79,7 +79,7 @@ transaction(vaultIdentifier: String, amount: UInt256, recipient: Address) { // // Set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) // Issue and store bridge-dedicated Provider Capability in storage if necessary if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc index 0a4d12b2..b5c15b55 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc @@ -57,7 +57,7 @@ transaction(vaultIdentifier: String, amount: UFix64, recipient: String) { // Withdraw the requested balance & set a cap on the withdrawable bridge fee self.sentVault <- vault.withdraw(amount: amount) var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) // Determine if the Vault requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.sentVault.getType()) diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc index fdc4e9ae..b60ea6ab 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc @@ -61,7 +61,7 @@ transaction(vaultIdentifier: String, amount: UFix64) { // Withdraw the requested balance & set a cap on the withdrawable bridge fee self.sentVault <- vault.withdraw(amount: amount) var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( - bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction + bytes: 400_000 // 400 kB as upper bound on movable storage used in a single transaction ) // Determine if the Vault requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.sentVault.getType()) From e13df7ea86fd95b8bb5711d54c5034936f88fe0f Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:22:06 -0600 Subject: [PATCH 28/31] add batch nft bridge txn test coverage --- cadence/tests/flow_evm_bridge_tests.cdc | 175 ++++++++++++++++++------ 1 file changed, 136 insertions(+), 39 deletions(-) diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index 12b61784..3a95166e 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -25,7 +25,8 @@ access(all) let exampleNFTIdentifier = "A.0000000000000008.ExampleNFT.NFT" access(all) let exampleNFTTokenName = "Example NFT" access(all) let exampleNFTTokenDescription = "Example NFT token description" access(all) let exampleNFTTokenThumbnail = "https://examplenft.com/thumbnail.png" -access(all) var mintedNFTID: UInt64 = 0 +access(all) var mintedNFTID1: UInt64 = 0 +access(all) var mintedNFTID2: UInt64 = 0 // ExampleToken access(all) let exampleTokenIdentifier = "A.0000000000000010.ExampleToken.Vault" @@ -516,22 +517,38 @@ fun testMintExampleNFTSucceeds() { Test.expect(hasCollection, Test.beSucceeded()) Test.assertEqual(true, hasCollection.returnValue as! Bool? ?? panic("Problem getting collection status")) - let mintExampleNFTResult = executeTransaction( + var mintExampleNFTResult = executeTransaction( "../transactions/example-assets/example-nft/mint_nft.cdc", [alice.address, exampleNFTTokenName, exampleNFTTokenDescription, exampleNFTTokenThumbnail, [], [], []], exampleNFTAccount ) Test.expect(mintExampleNFTResult, Test.beSucceeded()) - let aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") + var aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") Test.assertEqual(1, aliceOwnedIDs.length) - let events = Test.eventsOfType(Type()) + var events = Test.eventsOfType(Type()) Test.assertEqual(1, events.length) - let evt = events[0] as! NonFungibleToken.Deposited - mintedNFTID = evt.id + var evt = events[0] as! NonFungibleToken.Deposited + mintedNFTID1 = evt.id + + mintExampleNFTResult = executeTransaction( + "../transactions/example-assets/example-nft/mint_nft.cdc", + [alice.address, exampleNFTTokenName, exampleNFTTokenDescription, exampleNFTTokenThumbnail, [], [], []], + exampleNFTAccount + ) + Test.expect(mintExampleNFTResult, Test.beSucceeded()) + + aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") + Test.assertEqual(2, aliceOwnedIDs.length) + + events = Test.eventsOfType(Type()) + Test.assertEqual(2, events.length) + evt = events[1] as! NonFungibleToken.Deposited + mintedNFTID2 = evt.id - Test.assertEqual(aliceOwnedIDs[0], mintedNFTID) + Test.assert(mintedNFTID1 != mintedNFTID2) + Test.assertEqual(true, aliceOwnedIDs.contains(mintedNFTID1) && aliceOwnedIDs.contains(mintedNFTID2)) } access(all) @@ -694,8 +711,7 @@ fun testOnboardAndBridgeNFTToEVMSucceeds() { var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) var aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") - Test.assertEqual(1, aliceOwnedIDs.length) - let aliceID = aliceOwnedIDs[0] + Test.assertEqual(2, aliceOwnedIDs.length) var requiresOnboarding = typeRequiresOnboardingByIdentifier(exampleNFTIdentifier) ?? panic("Problem getting onboarding status for type") @@ -705,7 +721,7 @@ fun testOnboardAndBridgeNFTToEVMSucceeds() { bridgeNFTToEVM( signer: alice, nftIdentifier: exampleNFTIdentifier, - nftID: aliceID, + nftID: mintedNFTID1, bridgeAccountAddr: bridgeAccount.address, beFailed: false ) @@ -726,12 +742,12 @@ fun testOnboardAndBridgeNFTToEVMSucceeds() { // Confirm the NFT is no longer in Alice's Collection aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") - Test.assertEqual(0, aliceOwnedIDs.length) + Test.assertEqual(1, aliceOwnedIDs.length) // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation let isOwnerResult = executeScript( "../scripts/utils/is_owner.cdc", - [UInt256(mintedNFTID), aliceCOAAddressHex, associatedEVMAddressHex] + [UInt256(mintedNFTID1), aliceCOAAddressHex, associatedEVMAddressHex] ) Test.expect(isOwnerResult, Test.beSucceeded()) Test.assertEqual(true, isOwnerResult.returnValue as! Bool? ?? panic("Problem getting owner status")) @@ -744,8 +760,7 @@ fun testOnboardAndCrossVMTransferNFTToEVMSucceeds() { var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) var aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") - Test.assertEqual(1, aliceOwnedIDs.length) - let aliceID = aliceOwnedIDs[0] + Test.assertEqual(2, aliceOwnedIDs.length) let recipient = getCOAAddressHex(atFlowAddress: bob.address) @@ -756,7 +771,7 @@ fun testOnboardAndCrossVMTransferNFTToEVMSucceeds() { // Execute bridge NFT to EVM recipient - should also onboard the NFT type let crossVMTransferResult = executeTransaction( "../transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc", - [ exampleNFTIdentifier, aliceID, recipient ], + [ exampleNFTIdentifier, mintedNFTID1, recipient ], alice ) Test.expect(crossVMTransferResult, Test.beSucceeded()) @@ -777,15 +792,11 @@ fun testOnboardAndCrossVMTransferNFTToEVMSucceeds() { // Confirm the NFT is no longer in Alice's Collection aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") - Test.assertEqual(0, aliceOwnedIDs.length) + Test.assertEqual(1, aliceOwnedIDs.length) // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation - let isOwnerResult = executeScript( - "../scripts/utils/is_owner.cdc", - [UInt256(mintedNFTID), recipient, associatedEVMAddressHex] - ) - Test.expect(isOwnerResult, Test.beSucceeded()) - Test.assertEqual(true, isOwnerResult.returnValue as! Bool? ?? panic("Problem getting owner status")) + var aliceIsOwner = isOwner(of: UInt256(mintedNFTID1), ownerEVMAddrHex: recipient, erc721AddressHex: associatedEVMAddressHex) + Test.assertEqual(true, aliceIsOwner) } access(all) @@ -1085,7 +1096,7 @@ fun testPauseBridgeSucceeds() { Test.assertEqual(true, isPausedResult.returnValue as! Bool? ?? panic("Problem getting pause status")) var aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") - Test.assertEqual(1, aliceOwnedIDs.length) + Test.assertEqual(2, aliceOwnedIDs.length) var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) @@ -1111,8 +1122,10 @@ fun testPauseBridgeSucceeds() { access(all) fun testBridgeCadenceNativeNFTToEVMSucceeds() { + snapshot = getCurrentBlockHeight() + var aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") - Test.assertEqual(1, aliceOwnedIDs.length) + Test.assertEqual(2, aliceOwnedIDs.length) var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) @@ -1120,7 +1133,7 @@ fun testBridgeCadenceNativeNFTToEVMSucceeds() { bridgeNFTToEVM( signer: alice, nftIdentifier: exampleNFTIdentifier, - nftID: aliceOwnedIDs[0], + nftID: mintedNFTID1, bridgeAccountAddr: bridgeAccount.address, beFailed: false ) @@ -1130,26 +1143,110 @@ fun testBridgeCadenceNativeNFTToEVMSucceeds() { // Confirm the NFT is no longer in Alice's Collection aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") - Test.assertEqual(0, aliceOwnedIDs.length) + Test.assertEqual(1, aliceOwnedIDs.length) // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation let isOwnerResult = executeScript( "../scripts/utils/is_owner.cdc", - [UInt256(mintedNFTID), aliceCOAAddressHex, associatedEVMAddressHex] + [UInt256(mintedNFTID1), aliceCOAAddressHex, associatedEVMAddressHex] ) Test.expect(isOwnerResult, Test.beSucceeded()) Test.assertEqual(true, isOwnerResult.returnValue as! Bool? ?? panic("Problem getting owner status")) - let isNFTLocked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID) + let isNFTLocked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID1) Test.assertEqual(true, isNFTLocked) - let metadata = resolveLockedNFTView(bridgeAddress: bridgeAccount.address, nftTypeIdentifier: exampleNFTIdentifier, id: UInt256(mintedNFTID), viewIdentifier: Type().identifier) + let metadata = resolveLockedNFTView(bridgeAddress: bridgeAccount.address, nftTypeIdentifier: exampleNFTIdentifier, id: UInt256(mintedNFTID1), viewIdentifier: Type().identifier) Test.assert(metadata != nil, message: "Expected NFT metadata to be resolved from escrow but none was returned") } +access(all) +fun testBatchBridgeCadenceNativeNFTToEVMSucceeds() { + let tmp = snapshot + Test.reset(to: snapshot) + snapshot = tmp + + var aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") + Test.assertEqual(2, aliceOwnedIDs.length) + + var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + + // Execute bridge to EVM + let bridgeResult = executeTransaction( + "../transactions/bridge/nft/batch_bridge_nft_to_evm.cdc", + [ exampleNFTIdentifier, aliceOwnedIDs ], + alice + ) + Test.expect(bridgeResult, Test.beSucceeded()) + + let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleNFTIdentifier) + Test.assertEqual(40, associatedEVMAddressHex.length) + + // Confirm the NFT is no longer in Alice's Collection + aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") + Test.assertEqual(0, aliceOwnedIDs.length) + + // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation + var aliceIsOwner = isOwner(of: UInt256(mintedNFTID1), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + Test.assertEqual(true, aliceIsOwner) + aliceIsOwner = isOwner(of: UInt256(mintedNFTID2), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + Test.assertEqual(true, aliceIsOwner) + + let isNFT1Locked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID1) + let isNFT2Locked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID2) + Test.assertEqual(true, isNFT1Locked) + Test.assertEqual(true, isNFT2Locked) + + let metadata1 = resolveLockedNFTView(bridgeAddress: bridgeAccount.address, nftTypeIdentifier: exampleNFTIdentifier, id: UInt256(mintedNFTID1), viewIdentifier: Type().identifier) + let metadata2 = resolveLockedNFTView(bridgeAddress: bridgeAccount.address, nftTypeIdentifier: exampleNFTIdentifier, id: UInt256(mintedNFTID2), viewIdentifier: Type().identifier) + Test.assert(metadata1 != nil, message: "Expected NFT metadata to be resolved from escrow but none was returned") + Test.assert(metadata2 != nil, message: "Expected NFT metadata to be resolved from escrow but none was returned") +} + +access(all) +fun testBatchBridgeCadenceNativeNFTFromEVMSucceeds() { + snapshot = getCurrentBlockHeight() + + var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleNFTIdentifier) + Test.assertEqual(40, associatedEVMAddressHex.length) + + // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation + var aliceIsOwner = isOwner(of: UInt256(mintedNFTID1), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + Test.assertEqual(true, aliceIsOwner) + aliceIsOwner = isOwner(of: UInt256(mintedNFTID2), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + Test.assertEqual(true, aliceIsOwner) + + // Execute bridge from EVM + let bridgeResult = executeTransaction( + "../transactions/bridge/nft/batch_bridge_nft_from_evm.cdc", + [ exampleNFTIdentifier, [UInt256(mintedNFTID1), UInt256(mintedNFTID2)] ], + alice + ) + Test.expect(bridgeResult, Test.beSucceeded()) + + // Confirm the NFT is no longer in Alice's Collection + let aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") + Test.assertEqual(2, aliceOwnedIDs.length) + + // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation + aliceIsOwner = isOwner(of: UInt256(mintedNFTID1), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + Test.assertEqual(false, aliceIsOwner) + aliceIsOwner = isOwner(of: UInt256(mintedNFTID2), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + Test.assertEqual(false, aliceIsOwner) + + let isNFT1Locked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID1) + let isNFT2Locked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID2) + Test.assertEqual(false, isNFT1Locked) + Test.assertEqual(false, isNFT2Locked) +} + access(all) fun testCrossVMTransferCadenceNativeNFTFromEVMSucceeds() { + let tmp = snapshot + Test.reset(to: snapshot) snapshot = getCurrentBlockHeight() + // Configure recipient's Collection first, using generic setup transaction let setupCollectionResult = executeTransaction( "../transactions/example-assets/setup/setup_generic_nft_collection.cdc", @@ -1164,27 +1261,27 @@ fun testCrossVMTransferCadenceNativeNFTFromEVMSucceeds() { Test.assertEqual(40, associatedEVMAddressHex.length) // Assert ownership of the bridged NFT in EVM - var aliceIsOwner = isOwner(of: UInt256(mintedNFTID), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + var aliceIsOwner = isOwner(of: UInt256(mintedNFTID1), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) Test.assertEqual(true, aliceIsOwner) // Execute bridge NFT from EVM to Cadence recipient (Bob in this case) let crossVMTransferResult = executeTransaction( "../transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc", - [ exampleNFTIdentifier, UInt256(mintedNFTID), bob.address ], + [ exampleNFTIdentifier, UInt256(mintedNFTID1), bob.address ], alice ) Test.expect(crossVMTransferResult, Test.beSucceeded()) // Assert ownership of the bridged NFT in EVM has transferred - aliceIsOwner = isOwner(of: UInt256(mintedNFTID), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + aliceIsOwner = isOwner(of: UInt256(mintedNFTID1), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) Test.assertEqual(false, aliceIsOwner) // Assert the NFT is now in Bob's Collection let bobOwnedIDs = getIDs(ownerAddr: bob.address, storagePathIdentifier: "cadenceExampleNFTCollection") Test.assertEqual(1, bobOwnedIDs.length) - Test.assertEqual(mintedNFTID, bobOwnedIDs[0]) + Test.assertEqual(mintedNFTID1, bobOwnedIDs[0]) - let isNFTLocked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID) + let isNFTLocked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID1) Test.assertEqual(false, isNFTLocked) } @@ -1197,26 +1294,26 @@ fun testBridgeCadenceNativeNFTFromEVMSucceeds() { Test.assertEqual(40, associatedEVMAddressHex.length) // Assert ownership of the bridged NFT in EVM - var aliceIsOwner = isOwner(of: UInt256(mintedNFTID), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + var aliceIsOwner = isOwner(of: UInt256(mintedNFTID1), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) Test.assertEqual(true, aliceIsOwner) // Execute bridge from EVM bridgeNFTFromEVM( signer: alice, nftIdentifier: exampleNFTIdentifier, - erc721ID: UInt256(mintedNFTID), + erc721ID: UInt256(mintedNFTID1), bridgeAccountAddr: bridgeAccount.address, beFailed: false ) // Assert ownership of the bridged NFT in EVM has transferred - aliceIsOwner = isOwner(of: UInt256(mintedNFTID), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) + aliceIsOwner = isOwner(of: UInt256(mintedNFTID1), ownerEVMAddrHex: aliceCOAAddressHex, erc721AddressHex: associatedEVMAddressHex) Test.assertEqual(false, aliceIsOwner) // Assert the NFT is back in Alice's Collection let aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") Test.assertEqual(1, aliceOwnedIDs.length) - Test.assertEqual(mintedNFTID, aliceOwnedIDs[0]) + Test.assertEqual(true, aliceOwnedIDs.contains(mintedNFTID1)) } access(all) @@ -1282,7 +1379,7 @@ fun testPauseByTypeSucceeds() { bridgeNFTToEVM( signer: alice, nftIdentifier: exampleNFTIdentifier, - nftID: aliceOwnedIDs[0], + nftID: mintedNFTID1, bridgeAccountAddr: bridgeAccount.address, beFailed: true ) From 369ec9021b2a6219d0483214ebe64397d1da18b3 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:52:24 -0600 Subject: [PATCH 29/31] Apply suggestions from code review Co-authored-by: Joshua Hannan --- .../transactions/bridge/nft/batch_bridge_nft_from_evm.cdc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc index 36ea1574..d8acccd3 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc @@ -48,7 +48,9 @@ transaction(nftIdentifier: String, ids: [UInt256]) { // // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) - ?? panic("Could not borrow ViewResolver from NFT contract") + ?? panic("Could not borrow ViewResolver from NFT contract with name " + .concat(contractName).concat(" and address ") + .concat(contractAddress.toString()) let collectionData = viewResolver.resolveContractView( resourceType: self.nftType, viewType: Type() @@ -60,7 +62,8 @@ transaction(nftIdentifier: String, ids: [UInt256]) { signer.capabilities.publish(collectionCap, at: collectionData.publicPath) } self.collection = signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) - ?? panic("Could not borrow collection from storage path") + ?? panic("Could not borrow a NonFungibleToken Collection from the signer's storage path ") + .concat(collectionData.storagePath.toString())) /* --- Configure a ScopedFTProvider --- */ // From 1dd624cba329dbfd49fea40262f6fc6f2cf2c00d Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:08:24 -0600 Subject: [PATCH 30/31] fix failing test cases --- .../transactions/bridge/nft/batch_bridge_nft_from_evm.cdc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc index d8acccd3..df39eda5 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc @@ -49,8 +49,8 @@ transaction(nftIdentifier: String, ids: [UInt256]) { // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) ?? panic("Could not borrow ViewResolver from NFT contract with name " - .concat(contractName).concat(" and address ") - .concat(contractAddress.toString()) + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) let collectionData = viewResolver.resolveContractView( resourceType: self.nftType, viewType: Type() @@ -62,7 +62,7 @@ transaction(nftIdentifier: String, ids: [UInt256]) { signer.capabilities.publish(collectionCap, at: collectionData.publicPath) } self.collection = signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) - ?? panic("Could not borrow a NonFungibleToken Collection from the signer's storage path ") + ?? panic("Could not borrow a NonFungibleToken Collection from the signer's storage path " .concat(collectionData.storagePath.toString())) /* --- Configure a ScopedFTProvider --- */ From d043d986f8dca780b6bad06828195d2038f826b2 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:15:17 -0600 Subject: [PATCH 31/31] update bridging txn error messages --- .../bridge/nft/batch_bridge_nft_from_evm.cdc | 14 +++++++------ ...atch_bridge_nft_to_any_cadence_address.cdc | 16 +++++++++------ .../batch_bridge_nft_to_any_evm_address.cdc | 17 ++++++++++------ .../bridge/nft/batch_bridge_nft_to_evm.cdc | 17 ++++++++++------ .../bridge/nft/bridge_nft_from_evm.cdc | 17 ++++++++++------ .../nft/bridge_nft_to_any_cadence_address.cdc | 16 +++++++++------ .../nft/bridge_nft_to_any_evm_address.cdc | 15 +++++++++----- .../bridge/nft/bridge_nft_to_evm.cdc | 17 ++++++++++------ .../bridge/tokens/bridge_tokens_from_evm.cdc | 20 +++++++++++++------ .../bridge_tokens_to_any_cadence_address.cdc | 20 +++++++++++++------ .../bridge_tokens_to_any_evm_address.cdc | 14 ++++++++----- .../bridge/tokens/bridge_tokens_to_evm.cdc | 16 +++++++++------ 12 files changed, 129 insertions(+), 70 deletions(-) diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc index df39eda5..d2beb2a9 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc @@ -31,7 +31,7 @@ transaction(nftIdentifier: String, ids: [UInt256]) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the NFT type --- */ // @@ -49,12 +49,13 @@ transaction(nftIdentifier: String, ids: [UInt256]) { // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) ?? panic("Could not borrow ViewResolver from NFT contract with name " - .concat(nftContractName).concat(" and address ") - .concat(nftContractAddress.toString())) + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) let collectionData = viewResolver.resolveContractView( resourceType: self.nftType, viewType: Type() - ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(self.nftType.identifier)) if signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) == nil { signer.storage.save(<-collectionData.createEmptyCollection(), to: collectionData.storagePath) signer.capabilities.unpublish(collectionData.publicPath) @@ -81,7 +82,8 @@ transaction(nftIdentifier: String, ids: [UInt256]) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, @@ -102,7 +104,7 @@ transaction(nftIdentifier: String, ids: [UInt256]) { // Ensure the bridged nft is the correct type assert( nft.getType() == self.nftType, - message: "Bridged nft type mismatch - requeswted: ".concat(self.nftType.identifier) + message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier) .concat(", received: ").concat(nft.getType().identifier) ) // Deposit the bridged NFT into the signer's collection diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc index 686d26bb..9de56dd1 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_cadence_address.cdc @@ -33,7 +33,7 @@ transaction(nftIdentifier: String, ids: [UInt256], recipient: Address) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the NFT type --- */ // @@ -50,11 +50,14 @@ transaction(nftIdentifier: String, ids: [UInt256], recipient: Address) { // // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) - ?? panic("Could not borrow ViewResolver from NFT contract") + ?? panic("Could not borrow ViewResolver from NFT contract with name " + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) let collectionData = viewResolver.resolveContractView( resourceType: self.nftType, viewType: Type() - ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(self.nftType.identifier)) // Configure the signer's account for this NFT if signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) == nil { signer.storage.save(<-collectionData.createEmptyCollection(), to: collectionData.storagePath) @@ -63,7 +66,7 @@ transaction(nftIdentifier: String, ids: [UInt256], recipient: Address) { signer.capabilities.publish(collectionCap, at: collectionData.publicPath) } self.receiver = getAccount(recipient).capabilities.borrow<&{NonFungibleToken.Receiver}>(collectionData.publicPath) - ?? panic("Could not borrow Receiver from recipient's public capability path") + ?? panic("Could not borrow NonFungibleToken Receiver from recipient's public capability path") /* --- Configure a ScopedFTProvider --- */ // @@ -81,7 +84,8 @@ transaction(nftIdentifier: String, ids: [UInt256], recipient: Address) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, @@ -102,7 +106,7 @@ transaction(nftIdentifier: String, ids: [UInt256], recipient: Address) { // Ensure the bridged nft is the correct type assert( nft.getType() == self.nftType, - message: "Bridged nft type mismatch - requeswted: ".concat(self.nftType.identifier) + message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier) .concat(", received: ").concat(nft.getType().identifier) ) // Deposit the bridged NFT into the signer's collection diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc index 0a2b9995..76b24d01 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc @@ -34,7 +34,7 @@ transaction(nftIdentifier: String, ids: [UInt64], recipient: String) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the NFT type --- */ // @@ -51,14 +51,18 @@ transaction(nftIdentifier: String, ids: [UInt64], recipient: String) { // // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) - ?? panic("Could not borrow ViewResolver from NFT contract") + ?? panic("Could not borrow ViewResolver from NFT contract with name " + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) let collectionData = viewResolver.resolveContractView( resourceType: self.nftType, viewType: Type() - ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(self.nftType.identifier)) self.collection = signer.storage.borrow( from: collectionData.storagePath - ) ?? panic("Could not access signer's NFT Collection") + ) ?? panic("Could not borrow a NonFungibleToken Collection from the signer's storage path " + .concat(collectionData.storagePath.toString())) // Withdraw the requested NFT & set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( @@ -66,7 +70,7 @@ transaction(nftIdentifier: String, ids: [UInt64], recipient: String) { ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) - ?? panic("Bridge does not support this asset type") + ?? panic("Bridge does not support the requested asset type ".concat(nftIdentifier)) // Add the onboarding fee if onboarding is necessary if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee @@ -84,7 +88,8 @@ transaction(nftIdentifier: String, ids: [UInt64], recipient: String) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, diff --git a/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc index 2997e855..e07c0c90 100644 --- a/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc +++ b/cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc @@ -33,7 +33,7 @@ transaction(nftIdentifier: String, ids: [UInt64]) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the NFT type --- */ // @@ -50,14 +50,18 @@ transaction(nftIdentifier: String, ids: [UInt64]) { // // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) - ?? panic("Could not borrow ViewResolver from NFT contract") + ?? panic("Could not borrow ViewResolver from NFT contract with name " + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) let collectionData = viewResolver.resolveContractView( resourceType: self.nftType, viewType: Type() - ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(self.nftType.identifier)) self.collection = signer.storage.borrow( from: collectionData.storagePath - ) ?? panic("Could not access signer's NFT Collection") + ) ?? panic("Could not borrow a NonFungibleToken Collection from the signer's storage path " + .concat(collectionData.storagePath.toString())) // Withdraw the requested NFT & set a cap on the withdrawable bridge fee var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( @@ -65,7 +69,7 @@ transaction(nftIdentifier: String, ids: [UInt64]) { ) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length)) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) - ?? panic("Bridge does not support this asset type") + ?? panic("Bridge does not support the requested asset type ".concat(nftIdentifier)) // Add the onboarding fee if onboarding is necessary if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee @@ -83,7 +87,8 @@ transaction(nftIdentifier: String, ids: [UInt64]) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, diff --git a/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc index 8c2b9907..43c73b06 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc @@ -31,7 +31,7 @@ transaction(nftIdentifier: String, id: UInt256) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the NFT type --- */ // @@ -48,11 +48,14 @@ transaction(nftIdentifier: String, id: UInt256) { // // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) - ?? panic("Could not borrow ViewResolver from NFT contract") + ?? panic("Could not borrow ViewResolver from NFT contract with name " + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) let collectionData = viewResolver.resolveContractView( resourceType: self.nftType, viewType: Type() - ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(self.nftType.identifier)) if signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) == nil { signer.storage.save(<-collectionData.createEmptyCollection(), to: collectionData.storagePath) signer.capabilities.unpublish(collectionData.publicPath) @@ -60,7 +63,8 @@ transaction(nftIdentifier: String, id: UInt256) { signer.capabilities.publish(collectionCap, at: collectionData.publicPath) } self.collection = signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) - ?? panic("Could not borrow collection from storage path") + ?? panic("Could not borrow a NonFungibleToken Collection from the signer's storage path " + .concat(collectionData.storagePath.toString())) /* --- Configure a ScopedFTProvider --- */ // @@ -78,7 +82,8 @@ transaction(nftIdentifier: String, id: UInt256) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, @@ -97,7 +102,7 @@ transaction(nftIdentifier: String, id: UInt256) { // Ensure the bridged nft is the correct type assert( nft.getType() == self.nftType, - message: "Bridged nft type mismatch - requeswted: ".concat(self.nftType.identifier) + message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier) .concat(", received: ").concat(nft.getType().identifier) ) // Deposit the bridged NFT into the signer's collection diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc index f90bed20..ada8f29f 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc @@ -33,7 +33,7 @@ transaction(nftIdentifier: String, id: UInt256, recipient: Address) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the NFT type --- */ // @@ -50,11 +50,14 @@ transaction(nftIdentifier: String, id: UInt256, recipient: Address) { // // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) - ?? panic("Could not borrow ViewResolver from NFT contract") + ?? panic("Could not borrow ViewResolver from NFT contract with name " + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) let collectionData = viewResolver.resolveContractView( resourceType: self.nftType, viewType: Type() - ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(self.nftType.identifier)) // Configure the signer's account for this NFT if signer.storage.borrow<&{NonFungibleToken.Collection}>(from: collectionData.storagePath) == nil { signer.storage.save(<-collectionData.createEmptyCollection(), to: collectionData.storagePath) @@ -63,7 +66,7 @@ transaction(nftIdentifier: String, id: UInt256, recipient: Address) { signer.capabilities.publish(collectionCap, at: collectionData.publicPath) } self.receiver = getAccount(recipient).capabilities.borrow<&{NonFungibleToken.Receiver}>(collectionData.publicPath) - ?? panic("Could not borrow Receiver from recipient's public capability path") + ?? panic("Could not borrow NonFungibleToken Receiver from recipient's public capability path") /* --- Configure a ScopedFTProvider --- */ // @@ -81,7 +84,8 @@ transaction(nftIdentifier: String, id: UInt256, recipient: Address) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, @@ -100,7 +104,7 @@ transaction(nftIdentifier: String, id: UInt256, recipient: Address) { // Ensure the bridged nft is the correct type assert( nft.getType() == self.nftType, - message: "Bridged nft type mismatch - requeswted: ".concat(self.nftType.identifier) + message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier) .concat(", received: ").concat(nft.getType().identifier) ) // Deposit the bridged NFT into the signer's collection diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc index 221be214..46953e1e 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc @@ -42,14 +42,18 @@ transaction(nftIdentifier: String, id: UInt64, recipient: String) { // // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) - ?? panic("Could not borrow ViewResolver from NFT contract") + ?? panic("Could not borrow ViewResolver from NFT contract with name " + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) let collectionData = viewResolver.resolveContractView( resourceType: nil, viewType: Type() - ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(nftType.identifier)) let collection = signer.storage.borrow( from: collectionData.storagePath - ) ?? panic("Could not access signer's NFT Collection") + ) ?? panic("Could not borrow a NonFungibleToken Collection from the signer's storage path " + .concat(collectionData.storagePath.toString())) // Withdraw the requested NFT & calculate the approximate bridge fee based on NFT storage usage self.nft <- collection.withdraw(withdrawID: id) @@ -58,7 +62,7 @@ transaction(nftIdentifier: String, id: UInt64, recipient: String) { ) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nft.getType()) - ?? panic("Bridge does not support this asset type") + ?? panic("Bridge does not support the requested asset type ".concat(nftIdentifier)) // Add the onboarding fee if onboarding is necessary if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee @@ -76,7 +80,8 @@ transaction(nftIdentifier: String, id: UInt64, recipient: String) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc index 449ef397..ed216f15 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc @@ -32,7 +32,7 @@ transaction(nftIdentifier: String, id: UInt64) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the NFT type --- */ // @@ -49,14 +49,18 @@ transaction(nftIdentifier: String, id: UInt64) { // // Borrow a reference to the NFT collection, configuring if necessary let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) - ?? panic("Could not borrow ViewResolver from NFT contract") + ?? panic("Could not borrow ViewResolver from NFT contract with name " + .concat(nftContractName).concat(" and address ") + .concat(nftContractAddress.toString())) let collectionData = viewResolver.resolveContractView( resourceType: nftType, viewType: Type() - ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(nftType.identifier)) let collection = signer.storage.borrow( from: collectionData.storagePath - ) ?? panic("Could not access signer's NFT Collection") + ) ?? panic("Could not borrow a NonFungibleToken Collection from the signer's storage path " + .concat(collectionData.storagePath.toString())) // Withdraw the requested NFT & set a cap on the withdrawable bridge fee self.nft <- collection.withdraw(withdrawID: id) @@ -65,7 +69,7 @@ transaction(nftIdentifier: String, id: UInt64) { ) // Determine if the NFT requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nft.getType()) - ?? panic("Bridge does not support this asset type") + ?? panic("Bridge does not support the requested asset type ".concat(nftIdentifier)) // Add the onboarding fee if onboarding is necessary if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee @@ -83,7 +87,8 @@ transaction(nftIdentifier: String, id: UInt64) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc index c2bb1f92..1f61be93 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc @@ -34,7 +34,7 @@ transaction(vaultIdentifier: String, amount: UInt256) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the Vault type --- */ // @@ -51,11 +51,14 @@ transaction(vaultIdentifier: String, amount: UInt256) { // // Borrow a reference to the FungibleToken Vault, configuring if necessary let viewResolver = getAccount(tokenContractAddress).contracts.borrow<&{ViewResolver}>(name: tokenContractName) - ?? panic("Could not borrow ViewResolver from FungibleToken contract") + ?? panic("Could not borrow ViewResolver from FungibleToken contract with name" + .concat(tokenContractName).concat(" and address ") + .concat(tokenContractAddress.toString())) let vaultData = viewResolver.resolveContractView( resourceType: self.vaultType, viewType: Type() - ) as! FungibleTokenMetadataViews.FTVaultData? ?? panic("Could not resolve FTVaultData view") + ) as! FungibleTokenMetadataViews.FTVaultData? + ?? panic("Could not resolve FTVaultData view for Vault type ".concat(self.vaultType.identifier)) // If the vault does not exist, create it and publish according to the contract's defined configuration if signer.storage.borrow<&{FungibleToken.Vault}>(from: vaultData.storagePath) == nil { signer.storage.save(<-vaultData.createEmptyVault(), to: vaultData.storagePath) @@ -70,7 +73,7 @@ transaction(vaultIdentifier: String, amount: UInt256) { signer.capabilities.publish(metadataCap, at: vaultData.metadataPath) } self.receiver = signer.storage.borrow<&{FungibleToken.Vault}>(from: vaultData.storagePath) - ?? panic("Could not borrow Vault from storage path") + ?? panic("Could not borrow FungibleToken Vault from storage path ".concat(vaultData.storagePath.toString())) /* --- Configure a ScopedFTProvider --- */ // @@ -88,7 +91,8 @@ transaction(vaultIdentifier: String, amount: UInt256) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, @@ -105,7 +109,11 @@ transaction(vaultIdentifier: String, amount: UInt256) { feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} ) // Ensure the bridged vault is the correct type - assert(vault.getType() == self.vaultType, message: "Bridged vault type mismatch") + assert( + vault.getType() == self.vaultType, + message: "Bridged vault type mismatch - requested: ".concat(self.vaultType.identifier) + .concat(", received: ").concat(vault.getType().identifier) + ) // Deposit the bridged token into the signer's vault self.receiver.deposit(from: <-vault) // Destroy the ScopedFTProvider diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc index 54d076eb..111e9249 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc @@ -37,7 +37,7 @@ transaction(vaultIdentifier: String, amount: UInt256, recipient: Address) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the Vault type --- */ // @@ -54,11 +54,14 @@ transaction(vaultIdentifier: String, amount: UInt256, recipient: Address) { // // Borrow a reference to the FungibleToken Vault, configuring if necessary let viewResolver = getAccount(tokenContractAddress).contracts.borrow<&{ViewResolver}>(name: tokenContractName) - ?? panic("Could not borrow ViewResolver from FungibleToken contract") + ?? panic("Could not borrow ViewResolver from FungibleToken contract with name" + .concat(tokenContractName).concat(" and address ") + .concat(tokenContractAddress.toString())) let vaultData = viewResolver.resolveContractView( resourceType: self.vaultType, viewType: Type() - ) as! FungibleTokenMetadataViews.FTVaultData? ?? panic("Could not resolve FTVaultData view") + ) as! FungibleTokenMetadataViews.FTVaultData? + ?? panic("Could not resolve FTVaultData view for Vault type ".concat(self.vaultType.identifier)) // If the vault does not exist, create it and publish according to the contract's defined configuration if signer.storage.borrow<&{FungibleToken.Vault}>(from: vaultData.storagePath) == nil { signer.storage.save(<-vaultData.createEmptyVault(), to: vaultData.storagePath) @@ -73,7 +76,7 @@ transaction(vaultIdentifier: String, amount: UInt256, recipient: Address) { signer.capabilities.publish(metadataCap, at: vaultData.metadataPath) } self.receiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(vaultData.receiverPath) - ?? panic("Could not borrow Vault from recipient's account") + ?? panic("Could not borrow FungibleToken Vault from storage path ".concat(vaultData.storagePath.toString())) /* --- Configure a ScopedFTProvider --- */ // @@ -91,7 +94,8 @@ transaction(vaultIdentifier: String, amount: UInt256, recipient: Address) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, @@ -108,7 +112,11 @@ transaction(vaultIdentifier: String, amount: UInt256, recipient: Address) { feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} ) // Ensure the bridged vault is the correct type - assert(vault.getType() == self.vaultType, message: "Bridged vault type mismatch") + assert( + vault.getType() == self.vaultType, + message: "Bridged vault type mismatch - requested: ".concat(self.vaultType.identifier) + .concat(", received: ").concat(vault.getType().identifier) + ) // Deposit the bridged token into the signer's vault self.receiver.deposit(from: <-vault) // Destroy the ScopedFTProvider diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc index b5c15b55..b82c6e9a 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc @@ -45,14 +45,17 @@ transaction(vaultIdentifier: String, amount: UFix64, recipient: String) { // // Borrow a reference to the FungibleToken Vault let viewResolver = getAccount(tokenContractAddress).contracts.borrow<&{ViewResolver}>(name: tokenContractName) - ?? panic("Could not borrow ViewResolver from FungibleToken contract") + ?? panic("Could not borrow ViewResolver from FungibleToken contract with name" + .concat(tokenContractName).concat(" and address ") + .concat(tokenContractAddress.toString())) let vaultData = viewResolver.resolveContractView( resourceType: nil, viewType: Type() - ) as! FungibleTokenMetadataViews.FTVaultData? ?? panic("Could not resolve FTVaultData view") + ) as! FungibleTokenMetadataViews.FTVaultData? + ?? panic("Could not resolve FTVaultData view for Vault type ".concat(vaultType.identifier)) let vault = signer.storage.borrow( from: vaultData.storagePath - ) ?? panic("Could not access signer's FungibleToken Vault") + ) ?? panic("Could not borrow FungibleToken Vault from storage path ".concat(vaultData.storagePath.toString())) // Withdraw the requested balance & set a cap on the withdrawable bridge fee self.sentVault <- vault.withdraw(amount: amount) @@ -61,7 +64,7 @@ transaction(vaultIdentifier: String, amount: UFix64, recipient: String) { ) // Determine if the Vault requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.sentVault.getType()) - ?? panic("Bridge does not support this asset type") + ?? panic("Bridge does not support the requested asset type ".concat(vaultIdentifier)) if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee } @@ -78,7 +81,8 @@ transaction(vaultIdentifier: String, amount: UFix64, recipient: String) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy, diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc index b60ea6ab..5371e43d 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc @@ -32,7 +32,7 @@ transaction(vaultIdentifier: String, amount: UFix64) { // // Borrow a reference to the signer's COA self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") + ?? panic("Could not borrow COA signer's account at path /storage/evm") /* --- Construct the Vault type --- */ // @@ -49,14 +49,17 @@ transaction(vaultIdentifier: String, amount: UFix64) { // // Borrow a reference to the FungibleToken Vault let viewResolver = getAccount(tokenContractAddress).contracts.borrow<&{ViewResolver}>(name: tokenContractName) - ?? panic("Could not borrow ViewResolver from FungibleToken contract") + ?? panic("Could not borrow ViewResolver from FungibleToken contract with name" + .concat(tokenContractName).concat(" and address ") + .concat(tokenContractAddress.toString())) let vaultData = viewResolver.resolveContractView( resourceType: vaultType, viewType: Type() - ) as! FungibleTokenMetadataViews.FTVaultData? ?? panic("Could not resolve FTVaultData view") + ) as! FungibleTokenMetadataViews.FTVaultData? + ?? panic("Could not resolve FTVaultData view for Vault type ".concat(vaultType.identifier)) let vault = signer.storage.borrow( from: vaultData.storagePath - ) ?? panic("Could not access signer's FungibleToken Vault") + ) ?? panic("Could not borrow FungibleToken Vault from storage path ".concat(vaultData.storagePath.toString())) // Withdraw the requested balance & set a cap on the withdrawable bridge fee self.sentVault <- vault.withdraw(amount: amount) @@ -65,7 +68,7 @@ transaction(vaultIdentifier: String, amount: UFix64) { ) // Determine if the Vault requires onboarding - this impacts the fee required self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.sentVault.getType()) - ?? panic("Bridge does not support this asset type") + ?? panic("Bridge does not support the requested asset type ".concat(vaultIdentifier)) if self.requiresOnboarding { approxFee = approxFee + FlowEVMBridgeConfig.onboardFee } @@ -82,7 +85,8 @@ transaction(vaultIdentifier: String, amount: UFix64) { // Copy the stored Provider capability and create a ScopedFTProvider let providerCapCopy = signer.storage.copy>( from: FlowEVMBridgeConfig.providerCapabilityStoragePath - ) ?? panic("Invalid Provider Capability found in storage.") + ) ?? panic("Invalid FungibleToken Provider Capability found in storage at path " + .concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( provider: providerCapCopy,