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 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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 10/11] 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 11/11] 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,