From a397da4ab191839cffd23776e3bb25218053aed5 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:42:16 -0600 Subject: [PATCH 1/7] add local file to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9f225640..abb57a87 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ coverage.lcov local.flow.json # Local -.DS_Store \ No newline at end of file +.DS_Store +.vscode/ \ No newline at end of file From 79c33a8d173d90c0cf09294580da8c3666076e98 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:17:38 -0600 Subject: [PATCH 2/7] add txns to cross VM txfr Cadence -> EVM --- cadence/tests/flow_evm_bridge_tests.cdc | 106 +++++++++++++++++- .../nft/bridge_nft_to_any_evm_address.cdc | 98 ++++++++++++++++ .../bridge_tokens_to_any_evm_address.cdc | 104 +++++++++++++++++ 3 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc create mode 100644 cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index 992b60e5..06efe10d 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -16,6 +16,7 @@ access(all) let exampleNFTAccount = Test.getAccount(0x0000000000000008) access(all) let exampleERCAccount = Test.getAccount(0x0000000000000009) access(all) let exampleTokenAccount = Test.getAccount(0x0000000000000010) access(all) let alice = Test.createAccount() +access(all) let bob = Test.createAccount() // ExampleNFT values access(all) let exampleNFTIdentifier = "A.0000000000000008.ExampleNFT.NFT" @@ -360,9 +361,12 @@ fun testDeployERC20Succeeds() { access(all) fun testCreateCOASucceeds() { transferFlow(signer: serviceAccount, recipient: alice.address, amount: 1_000.0) + transferFlow(signer: serviceAccount, recipient: bob.address, amount: 1_000.0) createCOA(signer: alice, fundingAmount: 100.0) + createCOA(signer: bob, fundingAmount: 100.0) - let coaAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + let aliceCOAAddress = getCOAAddressHex(atFlowAddress: alice.address) + let bobCOAAddress = getCOAAddressHex(atFlowAddress: bob.address) } access(all) @@ -590,7 +594,6 @@ fun testOnboardAndBridgeNFTToEVMSucceeds() { Test.reset(to: snapshot) var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) var aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") Test.assertEqual(1, aliceOwnedIDs.length) let aliceID = aliceOwnedIDs[0] @@ -636,6 +639,56 @@ fun testOnboardAndBridgeNFTToEVMSucceeds() { Test.assertEqual(true, isOwnerResult.returnValue as! Bool? ?? panic("Problem getting owner status")) } +access(all) +fun testOnboardAndCrossVMTransferNFTToEVMSucceeds() { + // Revert to state before ExampleNFT was onboarded + Test.reset(to: snapshot) + + var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + var aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: "cadenceExampleNFTCollection") + Test.assertEqual(1, aliceOwnedIDs.length) + let aliceID = aliceOwnedIDs[0] + + let recipient = getCOAAddressHex(atFlowAddress: bob.address) + + var requiresOnboarding = typeRequiresOnboardingByIdentifier(exampleNFTIdentifier) + ?? panic("Problem getting onboarding status for type") + Test.assertEqual(true, requiresOnboarding) + + // 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", + [ exampleNFTAccount.address, "ExampleNFT", aliceID, recipient ], + alice + ) + Test.expect(crossVMTransferResult, Test.beSucceeded()) + + requiresOnboarding = typeRequiresOnboardingByIdentifier(exampleNFTIdentifier) + ?? panic("Problem getting onboarding status for type") + Test.assertEqual(false, requiresOnboarding) + + let onboardingResult = executeTransaction( + "../transactions/bridge/onboarding/onboard_by_type_identifier.cdc", + [exampleNFTIdentifier], + alice + ) + Test.expect(onboardingResult, Test.beFailed()) + + 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 + 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")) +} access(all) fun testOnboardTokenByTypeSucceeds() { @@ -668,7 +721,6 @@ fun testOnboardAndBridgeTokensToEVMSucceeds() { Test.reset(to: snapshot) var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) var cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") ?? panic("Could not get ExampleToken balance") @@ -711,6 +763,54 @@ fun testOnboardAndBridgeTokensToEVMSucceeds() { Test.assertEqual(expectedEVMBalance, evmBalance) } +access(all) +fun testOnboardAndCrossVMTransferTokensToEVMSucceeds() { + // Revert to state before ExampleNFT was onboarded + Test.reset(to: snapshot) + + var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + var cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Could not get ExampleToken balance") + let recipient = getCOAAddressHex(atFlowAddress: bob.address) + + var requiresOnboarding = typeRequiresOnboardingByIdentifier(exampleTokenIdentifier) + ?? panic("Problem getting onboarding status for type") + Test.assertEqual(true, requiresOnboarding) + + // Execute bridge to EVM - should also onboard the token type + let crossVMTransferResult = executeTransaction( + "../transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc", + [ exampleTokenAccount.address, "ExampleToken", cadenceBalance, recipient ], + alice + ) + Test.expect(crossVMTransferResult, Test.beSucceeded()) + + requiresOnboarding = typeRequiresOnboardingByIdentifier(exampleTokenIdentifier) + ?? panic("Problem getting onboarding status for type") + Test.assertEqual(false, requiresOnboarding) + + let onboardingResult = executeTransaction( + "../transactions/bridge/onboarding/onboard_by_type_identifier.cdc", + [exampleTokenIdentifier], + alice + ) + Test.expect(onboardingResult, Test.beFailed()) + + let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleTokenIdentifier) + Test.assertEqual(40, associatedEVMAddressHex.length) + + // Confirm Alice's token balance is now 0.0 + cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + Test.assertEqual(0.0, cadenceBalance) + + // Confirm balance on EVM side has been updated + let decimals = getTokenDecimals(erc20AddressHex: associatedEVMAddressHex) + let expectedEVMBalance = ufix64ToUInt256(exampleTokenMintAmount, decimals: decimals) + let evmBalance = balanceOf(evmAddressHex: recipient, erc20AddressHex: associatedEVMAddressHex) + Test.assertEqual(expectedEVMBalance, evmBalance) +} + access(all) fun testBatchOnboardByTypeSucceeds() { Test.assert(snapshot != 0, message: "Expected snapshot to be taken before onboarding any types") 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 new file mode 100644 index 00000000..418b3bc7 --- /dev/null +++ b/cadence/transactions/bridge/nft/bridge_nft_to_any_evm_address.cdc @@ -0,0 +1,98 @@ +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 named recipient in EVM. +/// +/// 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 nftContractAddress: The Flow account address hosting the NFT-defining Cadence contract +/// @param nftContractName: The name of the NFT-defining Cadence contract +/// @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(nftContractAddress: Address, nftContractName: String, id: UInt64, recipient: String) { + + let nft: @{NonFungibleToken.NFT} + let requiresOnboarding: Bool + let scopedProvider: @ScopedFTProviders.ScopedFTProvider + + prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + /* --- 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: nil, + viewType: Type() + ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") + let collection = signer.storage.borrow( + 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 + self.nft <- collection.withdraw(withdrawID: id) + let withdrawnStorageUsage = signer.storage.used + var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( + bytes: currentStorageUsage - withdrawnStorageUsage + ) * 1.10 + // 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") + 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.nft.getType(), + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + } + // Execute the bridge transaction + let recipientEVMAddress = EVM.addressFromString(recipient) + FlowEVMBridge.bridgeNFTToEVM( + token: <-self.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/tokens/bridge_tokens_to_any_evm_address.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc new file mode 100644 index 00000000..4a45784d --- /dev/null +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc @@ -0,0 +1,104 @@ +import "FungibleToken" +import "FlowToken" +import "ViewResolver" +import "NonFungibleToken" +import "FungibleTokenMetadataViews" + +import "ScopedFTProviders" + +import "EVM" + +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +import "FlowEVMBridgeConfig" + +/// Bridges a Vault from the signer's storage to any EVM address. The full amount to be transferred is sourced from the +/// signer's Cadence Vault & it's assumed the signer has sufficient funds to cover the amount requested to be bridged. +/// +/// NOTE: The Vault being bridged must have first been onboarded to the bridge. This can be checked for with the method +/// FlowEVMBridge.typeRequiresOnboarding(type): Bool? +/// +/// @param tokenContractAddress: The Flow account address hosting the FT-defining Cadence contract +/// @param tokenContractName: The name of the Vault-defining Cadence contract +/// @param amount: The amount of tokens to bridge from EVM +/// @param recipient: The hex-encoded EVM address to send the tokens to +/// +transaction(tokenContractAddress: Address, tokenContractName: String, amount: UFix64, recipient: String) { + + let sentVault: @{FungibleToken.Vault} + let requiresOnboarding: Bool + let scopedProvider: @ScopedFTProviders.ScopedFTProvider + + prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + /* --- Retrieve the funds --- */ + // + // Borrow a reference to the FungibleToken Vault + let viewResolver = getAccount(tokenContractAddress).contracts.borrow<&{ViewResolver}>(name: tokenContractName) + ?? panic("Could not borrow ViewResolver from FungibleToken contract") + let vaultData = viewResolver.resolveContractView( + resourceType: nil, + viewType: Type() + ) as! FungibleTokenMetadataViews.FTVaultData? ?? panic("Could not resolve FTVaultData view") + let vault = signer.storage.borrow( + 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 + 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 + // 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") + 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 + ) + } + + pre { + self.sentVault.balance == amount: "Amount to be transferred does not match the requested amount" + } + + execute { + if self.requiresOnboarding { + // Onboard the Vault to the bridge + FlowEVMBridge.onboardByType( + self.sentVault.getType(), + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + } + // Execute the bridge transaction + let recipientEVMAddress = EVM.addressFromString(recipient) + FlowEVMBridge.bridgeTokensToEVM( + vault: <-self.sentVault, + to: recipientEVMAddress, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + // Destroy the ScopedFTProvider + destroy self.scopedProvider + } +} From c8c4fd0555a7add97e225cd339f010e54e866add Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 25 Jun 2024 17:45:53 -0600 Subject: [PATCH 3/7] add cross VM txfr to Cadence txns + tests --- cadence/tests/flow_evm_bridge_tests.cdc | 105 +++++++++++++++-- .../nft/bridge_nft_to_any_cadence_address.cdc | 101 ++++++++++++++++ .../bridge_tokens_to_any_cadence_address.cdc | 109 ++++++++++++++++++ .../setup/setup_generic_nft_collection.cdc | 31 +++++ .../setup/setup_generic_vault.cdc | 33 ++++++ 5 files changed, 370 insertions(+), 9 deletions(-) create mode 100644 cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc create mode 100644 cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc create mode 100644 cadence/transactions/example-assets/setup/setup_generic_nft_collection.cdc create mode 100644 cadence/transactions/example-assets/setup/setup_generic_vault.cdc diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index 06efe10d..b22c0141 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -182,7 +182,7 @@ fun setup() { arguments: [] ) Test.expect(err, Test.beNil()) - + /* Integrate EVM bridge contract */ // Set factory as registrar in registry @@ -335,7 +335,7 @@ fun testDeployERC721Succeeds() { exampleERCAccount ) Test.expect(erc721DeployResult, Test.beSucceeded()) - + // Get ERC721 & ERC20 deployed contract addresses let evts = Test.eventsOfType(Type()) Test.assertEqual(21, evts.length) @@ -350,12 +350,12 @@ fun testDeployERC20Succeeds() { exampleERCAccount ) Test.expect(erc20DeployResult, Test.beSucceeded()) - + // Get ERC721 & ERC20 deployed contract addresses let evts = Test.eventsOfType(Type()) Test.assertEqual(22, evts.length) erc20AddressHex = getEVMAddressHexFromEvents(evts, idx: 21) - + } access(all) @@ -601,7 +601,7 @@ fun testOnboardAndBridgeNFTToEVMSucceeds() { var requiresOnboarding = typeRequiresOnboardingByIdentifier(exampleNFTIdentifier) ?? panic("Problem getting onboarding status for type") Test.assertEqual(true, requiresOnboarding) - + // Execute bridge NFT to EVM - should also onboard the NFT type bridgeNFTToEVM( signer: alice, @@ -654,7 +654,7 @@ fun testOnboardAndCrossVMTransferNFTToEVMSucceeds() { var requiresOnboarding = typeRequiresOnboardingByIdentifier(exampleNFTIdentifier) ?? panic("Problem getting onboarding status for type") Test.assertEqual(true, requiresOnboarding) - + // 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", @@ -719,7 +719,7 @@ access(all) fun testOnboardAndBridgeTokensToEVMSucceeds() { // Revert to state before ExampleNFT was onboarded Test.reset(to: snapshot) - + var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) var cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") ?? panic("Could not get ExampleToken balance") @@ -767,7 +767,7 @@ access(all) fun testOnboardAndCrossVMTransferTokensToEVMSucceeds() { // Revert to state before ExampleNFT was onboarded Test.reset(to: snapshot) - + var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) var cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") ?? panic("Could not get ExampleToken balance") @@ -1029,8 +1029,47 @@ fun testBridgeCadenceNativeNFTToEVMSucceeds() { Test.assertEqual(true, isOwnerResult.returnValue as! Bool? ?? panic("Problem getting owner status")) } +access(all) +fun testCrossVMTransferCadenceNativeNFTFromEVMSucceeds() { + snapshot = getCurrentBlockHeight() + // Configure recipient's Collection first, using generic setup transaction + let setupCollectionResult = executeTransaction( + "../transactions/example-assets/setup/setup_generic_nft_collection.cdc", + [exampleNFTIdentifier], + bob + ) + Test.expect(setupCollectionResult, Test.beSucceeded()) + + let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + + let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleNFTIdentifier) + Test.assertEqual(40, associatedEVMAddressHex.length) + + // Assert ownership of the bridged NFT in EVM + var aliceIsOwner = isOwner(of: UInt256(mintedNFTID), 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", + [ exampleNFTAccount.address, "ExampleNFT", UInt256(mintedNFTID), 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) + 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]) +} + access(all) fun testBridgeCadenceNativeNFTFromEVMSucceeds() { + Test.reset(to: snapshot) let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleNFTIdentifier) @@ -1196,11 +1235,59 @@ fun testBridgeCadenceNativeTokenToEVMSucceeds() { Test.assertEqual(expectedEVMBalance, evmBalance) } +access(all) +fun testCrossVMTransferCadenceNativeTokenFromEVMSucceeds() { + snapshot = getCurrentBlockHeight() + // Configure recipient's Vault first, using generic setup transaction + let setupVaultResult = executeTransaction( + "../transactions/example-assets/setup/setup_generic_vault.cdc", + [exampleTokenIdentifier], + bob + ) + Test.expect(setupVaultResult, Test.beSucceeded()) + + let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleTokenIdentifier) + Test.assertEqual(40, associatedEVMAddressHex.length) + + var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + + // Confirm Alice is starting with 0.0 balance in their Cadence Vault + let preCadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + Test.assertEqual(0.0, preCadenceBalance) + + // Get Alice's ERC20 balance & convert to UFix64 + var evmBalance = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: associatedEVMAddressHex) + let decimals = getTokenDecimals(erc20AddressHex: associatedEVMAddressHex) + let ufixValue = uint256ToUFix64(evmBalance, decimals: decimals) + // Assert the converted balance is equal to the originally minted amount that was bridged in the previous step + Test.assertEqual(exampleTokenMintAmount, ufixValue) + + // Execute bridge tokens from EVM to Cadence recipient (Bob in this case) + let crossVMTransferResult = executeTransaction( + "../transactions/bridge/nft/bridge_tokens_to_any_cadence_address.cdc", + [ exampleTokenAccount.address, "ExampleToken", evmBalance, bob.address ], + alice + ) + Test.expect(crossVMTransferResult, Test.beSucceeded()) + + // Confirm ExampleToken balance has been bridged back to Alice's Cadence vault + let recipientCadenceBalance = getBalance(ownerAddr: bob.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + Test.assertEqual(ufixValue, recipientCadenceBalance) + + // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation + evmBalance = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: associatedEVMAddressHex) + Test.assertEqual(UInt256(0), evmBalance) +} + access(all) fun testBridgeCadenceNativeTokenFromEVMSucceeds() { + Test.reset(to: snapshot) + let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleTokenIdentifier) Test.assertEqual(40, associatedEVMAddressHex.length) - + var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) // Confirm Alice is starting with 0.0 balance in their Cadence Vault 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 new file mode 100644 index 00000000..f0212c1f --- /dev/null +++ b/cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc @@ -0,0 +1,101 @@ +import "FungibleToken" +import "NonFungibleToken" +import "ViewResolver" +import "MetadataViews" +import "FlowToken" + +import "ScopedFTProviders" + +import "EVM" + +import "FlowEVMBridge" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" + +/// This transaction bridges an NFT from EVM to Cadence assuming it 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 nftContractAddress: The Flow account address hosting the NFT-defining Cadence contract +/// @param nftContractName: The name of the NFT-defining Cadence contract +/// @param id: The ERC721 id of the NFT to bridge to Cadence from EVM +/// @param recipient: The Flow account address to receive the bridged NFT +/// +transaction(nftContractAddress: Address, nftContractName: String, id: 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") + + // Get the ERC721 contract address for the given NFT type + self.nftType = FlowEVMBridgeUtils.buildCompositeType( + address: nftContractAddress, + contractName: nftContractName, + resourceName: "NFT" + ) ?? panic("Could not construct NFT type") + + /* --- 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 --- */ + // + // Calculate the bridge fee - bridging from EVM consumes no storage, so flat fee + let approxFee = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0) + // 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 { + // Execute the bridge + let nft: @{NonFungibleToken.NFT} <- self.coa.withdrawNFT( + type: self.nftType, + id: id, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + // Deposit the bridged NFT into the signer's collection + self.receiver.deposit(token: <-nft) + // Destroy the ScopedFTProvider + destroy self.scopedProvider + } +} 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 new file mode 100644 index 00000000..becc344d --- /dev/null +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc @@ -0,0 +1,109 @@ +import "FungibleToken" +import "FungibleTokenMetadataViews" +import "ViewResolver" +import "MetadataViews" +import "FlowToken" + +import "ScopedFTProviders" + +import "EVM" + +import "FlowEVMBridge" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" + +/// This transaction bridges fungible tokens from EVM to Cadence assuming it has already been onboarded to the +/// FlowEVMBridge. The full amount to be transferred is sourced from EVM, so it's assumed the signer has sufficient +/// balance of the ERC20 to bridging into Cadence. Also know that the recipient Flow account must have a Receiver +/// capable of receiving the bridged tokens 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 tokenContractAddress: The Flow account address hosting the FT-defining Cadence contract +/// @param tokenContractName: The name of the Vault-defining Cadence contract +/// @param amount: The amount of tokens to bridge from EVM +/// @param recipient: The Flow account address to receive the bridged tokens +/// +transaction(tokenContractAddress: Address, tokenContractName: String, amount: UInt256, recipient: Address) { + + let vaultType: Type + let receiver: &{FungibleToken.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") + + // Get the ERC20 contract address for the given FungibleToken Vault type + self.vaultType = FlowEVMBridgeUtils.buildCompositeType( + address: tokenContractAddress, + contractName: tokenContractName, + resourceName: "Vault" + ) ?? panic("Could not construct Vault type of: " .concat(tokenContractAddress.toString()).concat(".").concat(tokenContractName).concat(".Vault")) + + /* --- Reference the signer's Vault --- */ + // + // 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") + let vaultData = viewResolver.resolveContractView( + resourceType: self.vaultType, + viewType: Type() + ) as! FungibleTokenMetadataViews.FTVaultData? ?? panic("Could not resolve FTVaultData view") + // 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) + + signer.capabilities.unpublish(vaultData.receiverPath) + signer.capabilities.unpublish(vaultData.metadataPath) + + let receiverCap = signer.capabilities.storage.issue<&{FungibleToken.Vault}>(vaultData.storagePath) + let metadataCap = signer.capabilities.storage.issue<&{FungibleToken.Vault}>(vaultData.storagePath) + + signer.capabilities.publish(receiverCap, at: vaultData.receiverPath) + 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") + + /* --- Configure a ScopedFTProvider --- */ + // + // Calculate the bridge fee - bridging from EVM consumes no storage, so flat fee + let approxFee = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0) + // 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 { + // Execute the bridge request + let vault: @{FungibleToken.Vault} <- self.coa.withdrawTokens( + type: self.vaultType, + amount: amount, + feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + // Deposit the bridged token into the signer's vault + self.receiver.deposit(from: <-vault) + // Destroy the ScopedFTProvider + destroy self.scopedProvider + } +} diff --git a/cadence/transactions/example-assets/setup/setup_generic_nft_collection.cdc b/cadence/transactions/example-assets/setup/setup_generic_nft_collection.cdc new file mode 100644 index 00000000..a4a5c664 --- /dev/null +++ b/cadence/transactions/example-assets/setup/setup_generic_nft_collection.cdc @@ -0,0 +1,31 @@ +import "NonFungibleToken" +import "MetadataViews" + +import "FlowEVMBridgeUtils" + +transaction(nftIdentifier: String) { + prepare(signer: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, UnpublishCapability) &Account) { + // Gather identifying information about the NFT and its defining contract + let nftType = CompositeType(nftIdentifier) ?? panic("Invalid NFT identifier: ".concat(nftIdentifier)) + let contractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: nftType) + ?? panic("Could not derive contract address from identifier: ".concat(nftIdentifier)) + let contractName = FlowEVMBridgeUtils.getContractName(fromType: nftType) + ?? panic("Could not derive contract name from identifier: ".concat(nftIdentifier)) + // Borrow the contract and resolve its collection data + let nftContract = getAccount(contractAddress).contracts.borrow<&{NonFungibleToken}>(name: contractName) + ?? panic("No such NFT contract found") + let data = nftContract.resolveContractView( + resourceType: nftType, + viewType: Type() + ) as! MetadataViews.NFTCollectionData? + ?? panic("Could not resolve collection data for NFT type: ".concat(nftIdentifier)) + + // Create a new collection and save it to signer's storage at the collection's default storage path + signer.storage.save(<-data.createEmptyCollection(), to: data.storagePath) + + // Issue a public Collection capability and publish it to the collection's default public path + signer.capabilities.unpublish(data.publicPath) + let receiverCap = signer.capabilities.storage.issue<&{NonFungibleToken.Collection}>(data.storagePath) + signer.capabilities.publish(receiverCap, at: data.publicPath) + } +} diff --git a/cadence/transactions/example-assets/setup/setup_generic_vault.cdc b/cadence/transactions/example-assets/setup/setup_generic_vault.cdc new file mode 100644 index 00000000..bb4b0700 --- /dev/null +++ b/cadence/transactions/example-assets/setup/setup_generic_vault.cdc @@ -0,0 +1,33 @@ +import "FungibleToken" +import "NonFungibleToken" +import "MetadataViews" +import "FungibleTokenMetadataViews" + +import "FlowEVMBridgeUtils" + +transaction(vaultIdentifier: String) { + prepare(signer: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, UnpublishCapability) &Account) { + // Gather identifying information about the Vault and its defining contract + let vaultType = CompositeType(vaultIdentifier) ?? panic("Invalid Vault identifier: ".concat(vaultIdentifier)) + let contractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: vaultType) + ?? panic("Could not derive contract address from identifier: ".concat(vaultIdentifier)) + let contractName = FlowEVMBridgeUtils.getContractName(fromType: vaultType) + ?? panic("Could not derive contract name from identifier: ".concat(vaultIdentifier)) + // Borrow the contract and resolve its Vault data + let ftContract = getAccount(contractAddress).contracts.borrow<&{FungibleToken}>(name: contractName) + ?? panic("No such FungibleToken contract found") + let data = ftContract.resolveContractView( + resourceType: vaultType, + viewType: Type() + ) as! FungibleTokenMetadataViews.FTVaultData? + ?? panic("Could not resolve collection data for Vault type: ".concat(vaultIdentifier)) + + // Create a new collection and save it to signer's storage at the collection's default storage path + signer.storage.save(<-data.createEmptyVault(), to: data.storagePath) + + // Issue a public Collection capability and publish it to the collection's default public path + signer.capabilities.unpublish(data.receiverPath) + let receiverCap = signer.capabilities.storage.issue<&{FungibleToken.Vault}>(data.storagePath) + signer.capabilities.publish(receiverCap, at: data.receiverPath) + } +} From 5bb8db3f9adf7fb617c398d9eecf836a6862fa97 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 25 Jun 2024 18:00:04 -0600 Subject: [PATCH 4/7] add transaction comments --- .../setup/setup_generic_nft_collection.cdc | 21 +++++++++++++++- .../setup/setup_generic_vault.cdc | 25 ++++++++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/cadence/transactions/example-assets/setup/setup_generic_nft_collection.cdc b/cadence/transactions/example-assets/setup/setup_generic_nft_collection.cdc index a4a5c664..ef7519b6 100644 --- a/cadence/transactions/example-assets/setup/setup_generic_nft_collection.cdc +++ b/cadence/transactions/example-assets/setup/setup_generic_nft_collection.cdc @@ -3,7 +3,13 @@ import "MetadataViews" import "FlowEVMBridgeUtils" +/// Configures a Collection according to the shared NonFungibleToken standard and the defaults specified by the NFT's +/// defining contract. +/// +/// @param nftIdentifier: The identifier of the NFT to configure. +/// transaction(nftIdentifier: String) { + prepare(signer: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, UnpublishCapability) &Account) { // Gather identifying information about the NFT and its defining contract let nftType = CompositeType(nftIdentifier) ?? panic("Invalid NFT identifier: ".concat(nftIdentifier)) @@ -18,7 +24,20 @@ transaction(nftIdentifier: String) { resourceType: nftType, viewType: Type() ) as! MetadataViews.NFTCollectionData? - ?? panic("Could not resolve collection data for NFT type: ".concat(nftIdentifier)) + ?? panic("Could not resolve NFTCollection data for NFT type: ".concat(nftIdentifier)) + + // Check for collision, returning if the collection already exists or reverting on unexpected collision + let storedType = signer.storage.type(at: data.storagePath) + if storedType == nftType { + return + } else if storedType != nil { + panic( + "Another resource of type " + .concat(storedType!.identifier) + .concat(" already exists at the storage path: ") + .concat(data.storagePath.toString()) + ) + } // Create a new collection and save it to signer's storage at the collection's default storage path signer.storage.save(<-data.createEmptyCollection(), to: data.storagePath) diff --git a/cadence/transactions/example-assets/setup/setup_generic_vault.cdc b/cadence/transactions/example-assets/setup/setup_generic_vault.cdc index bb4b0700..8c4b37fc 100644 --- a/cadence/transactions/example-assets/setup/setup_generic_vault.cdc +++ b/cadence/transactions/example-assets/setup/setup_generic_vault.cdc @@ -5,7 +5,13 @@ import "FungibleTokenMetadataViews" import "FlowEVMBridgeUtils" +/// Configures a Vault according to the shared FungibleToken standard and the defaults specified by the Vault's +/// defining contract. +/// +/// @param vaultIdentifier: The identifier of the Vault to configure. +/// transaction(vaultIdentifier: String) { + prepare(signer: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, UnpublishCapability) &Account) { // Gather identifying information about the Vault and its defining contract let vaultType = CompositeType(vaultIdentifier) ?? panic("Invalid Vault identifier: ".concat(vaultIdentifier)) @@ -20,12 +26,25 @@ transaction(vaultIdentifier: String) { resourceType: vaultType, viewType: Type() ) as! FungibleTokenMetadataViews.FTVaultData? - ?? panic("Could not resolve collection data for Vault type: ".concat(vaultIdentifier)) + ?? panic("Could not resolve FTVaultData for Vault type: ".concat(vaultIdentifier)) + + // Check for collision, returning if the vault already exists or reverting on unexpected collision + let storedType = signer.storage.type(at: data.storagePath) + if storedType == vaultType { + return + } else if storedType != nil { + panic( + "Another resource of type " + .concat(storedType!.identifier) + .concat(" already exists at the storage path: ") + .concat(data.storagePath.toString()) + ) + } - // Create a new collection and save it to signer's storage at the collection's default storage path + // Create a new vault and save it to signer's storage at the vault's default storage path signer.storage.save(<-data.createEmptyVault(), to: data.storagePath) - // Issue a public Collection capability and publish it to the collection's default public path + // Issue a public Vault capability and publish it to the vault's default public path signer.capabilities.unpublish(data.receiverPath) let receiverCap = signer.capabilities.storage.issue<&{FungibleToken.Vault}>(data.storagePath) signer.capabilities.publish(receiverCap, at: data.receiverPath) From acc71bab78b843bc1770ed2e071e4b47fb83fd01 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 25 Jun 2024 18:02:14 -0600 Subject: [PATCH 5/7] fix failing test case --- cadence/tests/flow_evm_bridge_tests.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index b22c0141..bca990e5 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -1265,7 +1265,7 @@ fun testCrossVMTransferCadenceNativeTokenFromEVMSucceeds() { // Execute bridge tokens from EVM to Cadence recipient (Bob in this case) let crossVMTransferResult = executeTransaction( - "../transactions/bridge/nft/bridge_tokens_to_any_cadence_address.cdc", + "../transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc", [ exampleTokenAccount.address, "ExampleToken", evmBalance, bob.address ], alice ) From 8667f8a832ee1a7b68ffebde6b1e6a59cd869a1e Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:15:07 -0600 Subject: [PATCH 6/7] Update cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc Co-authored-by: Joshua Hannan --- .../bridge/tokens/bridge_tokens_to_any_cadence_address.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 becc344d..e3c0ff06 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 @@ -17,7 +17,7 @@ import "FlowEVMBridgeUtils" /// balance of the ERC20 to bridging into Cadence. Also know that the recipient Flow account must have a Receiver /// capable of receiving the bridged tokens 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 +/// NOTE: The ERC20 must have first been onboarded to the bridge. This can be checked via the method /// FlowEVMBridge.evmAddressRequiresOnboarding(address: self.evmContractAddress) /// /// @param tokenContractAddress: The Flow account address hosting the FT-defining Cadence contract From dbd804dd4b71bd143be60638d311244ff3747a1d Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:03:02 -0600 Subject: [PATCH 7/7] update NFT & FT bridge txn interfaces to accept type identifier --- .../tests/flow_evm_bridge_handler_tests.cdc | 43 +++++++---- cadence/tests/flow_evm_bridge_tests.cdc | 72 ++++++++++--------- cadence/tests/test_helpers.cdc | 29 ++++---- .../bridge/nft/bridge_nft_from_evm.cdc | 21 +++--- .../nft/bridge_nft_to_any_cadence_address.cdc | 22 +++--- .../nft/bridge_nft_to_any_evm_address.cdc | 17 +++-- .../bridge/nft/bridge_nft_to_evm.cdc | 18 +++-- .../bridge/tokens/bridge_tokens_from_evm.cdc | 22 +++--- .../bridge_tokens_to_any_cadence_address.cdc | 24 ++++--- .../bridge_tokens_to_any_evm_address.cdc | 19 +++-- .../bridge/tokens/bridge_tokens_to_evm.cdc | 19 +++-- 11 files changed, 192 insertions(+), 114 deletions(-) diff --git a/cadence/tests/flow_evm_bridge_handler_tests.cdc b/cadence/tests/flow_evm_bridge_handler_tests.cdc index 94017183..628d8f10 100644 --- a/cadence/tests/flow_evm_bridge_handler_tests.cdc +++ b/cadence/tests/flow_evm_bridge_handler_tests.cdc @@ -455,8 +455,11 @@ fun testBridgeHandledCadenceNativeTokenToEVMFails() { // Execute bridge to EVM - should fail since Handler is not enabled bridgeTokensToEVM( signer: alice, - contractAddr: exampleHandledTokenAccount.address, - contractName: "ExampleHandledToken", + vaultIdentifier: buildTypeIdentifier( + address: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + resourceName: "Vault" + ), amount: cadenceBalance, beFailed: true ) @@ -474,8 +477,11 @@ fun testBridgeHandledCadenceNativeTokenFromEVMFails() { // Execute bridge from EVM bridgeTokensFromEVM( signer: alice, - contractAddr: exampleHandledTokenAccount.address, - contractName: "ExampleHandledToken", + vaultIdentifier: buildTypeIdentifier( + address: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + resourceName: "Vault" + ), amount: evmBalance, beFailed: true ) @@ -518,8 +524,11 @@ fun testBridgeHandledCadenceNativeTokenToEVMFirstSucceeds() { // Execute bridge to EVM bridgeTokensToEVM( signer: alice, - contractAddr: exampleHandledTokenAccount.address, - contractName: "ExampleHandledToken", + vaultIdentifier: buildTypeIdentifier( + address: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + resourceName: "Vault" + ), amount: cadenceBalance, beFailed: false ) @@ -557,8 +566,11 @@ fun testBridgeHandledCadenceNativeTokenFromEVMSecondSucceeds() { let ufixEVMbalance = uint256ToUFix64(evmBalance, decimals: defaultDecimals) bridgeTokensFromEVM( signer: alice, - contractAddr: exampleHandledTokenAccount.address, - contractName: "ExampleHandledToken", + vaultIdentifier: buildTypeIdentifier( + address: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + resourceName: "Vault" + ), amount: evmBalance, beFailed: false ) @@ -617,8 +629,11 @@ fun testBridgeHandledCadenceNativeTokenFromEVMFirstSucceeds() { // Execute bridge from EVM bridgeTokensFromEVM( signer: alice, - contractAddr: exampleHandledTokenAccount.address, - contractName: "ExampleHandledToken", + vaultIdentifier: buildTypeIdentifier( + address: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + resourceName: "Vault" + ), amount: aliceEVMBalanceBefore, beFailed: false ) @@ -666,9 +681,11 @@ fun testBridgeHandledCadenceNativeTokenToEVMSecondSucceeds() { // Execute bridge to EVM bridgeTokensToEVM( signer: alice, - contractAddr: exampleHandledTokenAccount.address, - contractName: "ExampleHandledToken", - amount: aliceCadenceBalanceBefore, + vaultIdentifier: buildTypeIdentifier( + address: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + resourceName: "Vault" + ), amount: aliceCadenceBalanceBefore, beFailed: false ) diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index bca990e5..6ab64d01 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -386,9 +386,11 @@ fun testBridgeFlowToEVMSucceeds() { let bridgeAmount = 100.0 bridgeTokensToEVM( signer: alice, - contractAddr: Address(0x03), - contractName: "FlowToken", - amount: bridgeAmount, + vaultIdentifier: buildTypeIdentifier( + address: Address(0x03), + contractName: "FlowToken", + resourceName: "Vault" + ), amount: bridgeAmount, beFailed: false ) @@ -605,8 +607,7 @@ fun testOnboardAndBridgeNFTToEVMSucceeds() { // Execute bridge NFT to EVM - should also onboard the NFT type bridgeNFTToEVM( signer: alice, - contractAddr: exampleNFTAccount.address, - contractName: "ExampleNFT", + nftIdentifier: exampleNFTIdentifier, nftID: aliceID, bridgeAccountAddr: bridgeAccount.address, beFailed: false @@ -658,7 +659,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", - [ exampleNFTAccount.address, "ExampleNFT", aliceID, recipient ], + [ exampleNFTIdentifier, aliceID, recipient ], alice ) Test.expect(crossVMTransferResult, Test.beSucceeded()) @@ -731,8 +732,7 @@ fun testOnboardAndBridgeTokensToEVMSucceeds() { // Execute bridge to EVM - should also onboard the token type bridgeTokensToEVM( signer: alice, - contractAddr: exampleTokenAccount.address, - contractName: "ExampleToken", + vaultIdentifier: exampleTokenIdentifier, amount: cadenceBalance, beFailed: false ) @@ -780,7 +780,7 @@ fun testOnboardAndCrossVMTransferTokensToEVMSucceeds() { // Execute bridge to EVM - should also onboard the token type let crossVMTransferResult = executeTransaction( "../transactions/bridge/tokens/bridge_tokens_to_any_evm_address.cdc", - [ exampleTokenAccount.address, "ExampleToken", cadenceBalance, recipient ], + [ exampleTokenIdentifier, cadenceBalance, recipient ], alice ) Test.expect(crossVMTransferResult, Test.beSucceeded()) @@ -973,8 +973,7 @@ fun testPauseBridgeSucceeds() { // Execute bridge to EVM - should fail after pausing bridgeNFTToEVM( signer: alice, - contractAddr: exampleNFTAccount.address, - contractName: "ExampleNFT", + nftIdentifier: exampleNFTIdentifier, nftID: aliceOwnedIDs[0], bridgeAccountAddr: bridgeAccount.address, beFailed: true @@ -1006,8 +1005,7 @@ fun testBridgeCadenceNativeNFTToEVMSucceeds() { // Execute bridge to EVM bridgeNFTToEVM( signer: alice, - contractAddr: exampleNFTAccount.address, - contractName: "ExampleNFT", + nftIdentifier: exampleNFTIdentifier, nftID: aliceOwnedIDs[0], bridgeAccountAddr: bridgeAccount.address, beFailed: false @@ -1052,7 +1050,7 @@ fun testCrossVMTransferCadenceNativeNFTFromEVMSucceeds() { // 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", - [ exampleNFTAccount.address, "ExampleNFT", UInt256(mintedNFTID), bob.address ], + [ exampleNFTIdentifier, UInt256(mintedNFTID), bob.address ], alice ) Test.expect(crossVMTransferResult, Test.beSucceeded()) @@ -1082,8 +1080,7 @@ fun testBridgeCadenceNativeNFTFromEVMSucceeds() { // Execute bridge from EVM bridgeNFTFromEVM( signer: alice, - contractAddr: exampleNFTAccount.address, - contractName: "ExampleNFT", + nftIdentifier: exampleNFTIdentifier, erc721ID: UInt256(mintedNFTID), bridgeAccountAddr: bridgeAccount.address, beFailed: false @@ -1108,9 +1105,11 @@ fun testBridgeEVMNativeNFTFromEVMSucceeds() { bridgeNFTFromEVM( signer: alice, - contractAddr: bridgeAccount.address, - contractName: derivedERC721ContractName, - erc721ID: erc721ID, + nftIdentifier: buildTypeIdentifier( + address: bridgeAccount.address, + contractName: derivedERC721ContractName, + resourceName: "NFT" + ), erc721ID: erc721ID, bridgeAccountAddr: bridgeAccount.address, beFailed: false ) @@ -1152,8 +1151,7 @@ fun testPauseByTypeSucceeds() { // Execute bridge to EVM - should fail after pausing bridgeNFTToEVM( signer: alice, - contractAddr: exampleNFTAccount.address, - contractName: "ExampleNFT", + nftIdentifier: exampleNFTIdentifier, nftID: aliceOwnedIDs[0], bridgeAccountAddr: bridgeAccount.address, beFailed: true @@ -1187,9 +1185,11 @@ fun testBridgeEVMNativeNFTToEVMSucceeds() { bridgeNFTToEVM( signer: alice, - contractAddr: bridgeAccount.address, - contractName: derivedERC721ContractName, - nftID: aliceOwnedIDs[0], + nftIdentifier: buildTypeIdentifier( + address: bridgeAccount.address, + contractName: derivedERC721ContractName, + resourceName: "NFT" + ), nftID: aliceOwnedIDs[0], bridgeAccountAddr: bridgeAccount.address, beFailed: false ) @@ -1214,8 +1214,7 @@ fun testBridgeCadenceNativeTokenToEVMSucceeds() { // Execute bridge to EVM bridgeTokensToEVM( signer: alice, - contractAddr: exampleTokenAccount.address, - contractName: "ExampleToken", + vaultIdentifier: exampleTokenIdentifier, amount: cadenceBalance, beFailed: false ) @@ -1266,7 +1265,7 @@ fun testCrossVMTransferCadenceNativeTokenFromEVMSucceeds() { // Execute bridge tokens from EVM to Cadence recipient (Bob in this case) let crossVMTransferResult = executeTransaction( "../transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc", - [ exampleTokenAccount.address, "ExampleToken", evmBalance, bob.address ], + [ exampleTokenIdentifier, evmBalance, bob.address ], alice ) Test.expect(crossVMTransferResult, Test.beSucceeded()) @@ -1305,8 +1304,7 @@ fun testBridgeCadenceNativeTokenFromEVMSucceeds() { // Execute bridge from EVM bridgeTokensFromEVM( signer: alice, - contractAddr: exampleTokenAccount.address, - contractName: "ExampleToken", + vaultIdentifier: exampleTokenIdentifier, amount: evmBalance, beFailed: false ) @@ -1339,9 +1337,11 @@ fun testBridgeEVMNativeTokenFromEVMSucceeds() { // Execute bridge from EVM bridgeTokensFromEVM( signer: alice, - contractAddr: bridgeAccount.address, - contractName: derivedERC20ContractName, - amount: evmBalance, + vaultIdentifier: buildTypeIdentifier( + address: bridgeAccount.address, + contractName: derivedERC20ContractName, + resourceName: "Vault" + ), amount: evmBalance, beFailed: false ) @@ -1388,9 +1388,11 @@ fun testBridgeEVMNativeTokenToEVMSucceeds() { // Execute bridge from EVM bridgeTokensToEVM( signer: alice, - contractAddr: bridgeAccount.address, - contractName: derivedERC20ContractName, - amount: cadenceBalance, + vaultIdentifier: buildTypeIdentifier( + address: bridgeAccount.address, + contractName: derivedERC20ContractName, + resourceName: "Vault" + ), amount: cadenceBalance, beFailed: false ) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 3fc5d8d2..e2c3fded 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -138,6 +138,15 @@ fun getEVMAddressHexFromEvents(_ evts: [AnyStruct], idx: Int): String { return hexAddress } +/* --- Derivation Helpers --- */ + +access(all) +fun buildTypeIdentifier(address: Address, contractName: String, resourceName: String): String { + return "A.".concat(address.toString().split(separator: "x")[1]).concat(".") + .concat(contractName).concat(".") + .concat(resourceName) +} + /* --- Script Helpers --- */ access(all) @@ -367,15 +376,14 @@ fun createCOA(signer: Test.TestAccount, fundingAmount: UFix64) { access(all) fun bridgeNFTToEVM( signer: Test.TestAccount, - contractAddr: Address, - contractName: String, + nftIdentifier: String, nftID: UInt64, bridgeAccountAddr: Address, beFailed: Bool ) { let bridgeResult = _executeTransaction( "../transactions/bridge/nft/bridge_nft_to_evm.cdc", - [contractAddr, contractName, nftID], + [nftIdentifier, nftID], signer ) if beFailed { @@ -399,15 +407,14 @@ fun bridgeNFTToEVM( access(all) fun bridgeNFTFromEVM( signer: Test.TestAccount, - contractAddr: Address, - contractName: String, + nftIdentifier: String, erc721ID: UInt256, bridgeAccountAddr: Address, beFailed: Bool ) { let bridgeResult = _executeTransaction( "../transactions/bridge/nft/bridge_nft_from_evm.cdc", - [contractAddr, contractName, erc721ID], + [nftIdentifier, erc721ID], signer ) if beFailed { @@ -429,14 +436,13 @@ fun bridgeNFTFromEVM( access(all) fun bridgeTokensToEVM( signer: Test.TestAccount, - contractAddr: Address, - contractName: String, + vaultIdentifier: String, amount: UFix64, beFailed: Bool ) { let bridgeResult = _executeTransaction( "../transactions/bridge/tokens/bridge_tokens_to_evm.cdc", - [contractAddr, contractName, amount], + [vaultIdentifier, amount], signer ) Test.expect(bridgeResult, beFailed ? Test.beFailed() : Test.beSucceeded()) @@ -448,14 +454,13 @@ fun bridgeTokensToEVM( access(all) fun bridgeTokensFromEVM( signer: Test.TestAccount, - contractAddr: Address, - contractName: String, + vaultIdentifier: String, amount: UInt256, beFailed: Bool ) { let bridgeResult = _executeTransaction( "../transactions/bridge/tokens/bridge_tokens_from_evm.cdc", - [contractAddr, contractName, amount], + [vaultIdentifier, amount], signer ) Test.expect(bridgeResult, beFailed ? Test.beFailed() : Test.beSucceeded()) diff --git a/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc b/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc index 718e2710..01bf3157 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_from_evm.cdc @@ -16,11 +16,10 @@ import "FlowEVMBridgeUtils" /// NOTE: The ERC721 must have first been onboarded to the bridge. This can be checked via the method /// FlowEVMBridge.evmAddressRequiresOnboarding(address: self.evmContractAddress) /// -/// @param nftContractAddress: The Flow account address hosting the NFT-defining Cadence contract -/// @param nftContractName: The name of the NFT-defining Cadence contract +/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier /// @param id: The ERC721 id of the NFT to bridge to Cadence from EVM /// -transaction(nftContractAddress: Address, nftContractName: String, id: UInt256) { +transaction(nftIdentifier: String, id: UInt256) { let nftType: Type let collection: &{NonFungibleToken.Collection} @@ -34,12 +33,16 @@ transaction(nftContractAddress: Address, nftContractName: String, id: UInt256) { self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow COA from provided gateway address") - // Get the ERC721 contract address for the given NFT type - self.nftType = FlowEVMBridgeUtils.buildCompositeType( - address: nftContractAddress, - contractName: nftContractName, - resourceName: "NFT" - ) ?? panic("Could not construct NFT type") + /* --- 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 --- */ // 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 f0212c1f..63bb9669 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 @@ -2,7 +2,6 @@ import "FungibleToken" import "NonFungibleToken" import "ViewResolver" import "MetadataViews" -import "FlowToken" import "ScopedFTProviders" @@ -18,12 +17,11 @@ import "FlowEVMBridgeUtils" /// NOTE: The ERC721 must have first been onboarded to the bridge. This can be checked via the method /// FlowEVMBridge.evmAddressRequiresOnboarding(address: self.evmContractAddress) /// -/// @param nftContractAddress: The Flow account address hosting the NFT-defining Cadence contract -/// @param nftContractName: The name of the NFT-defining Cadence contract +/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier /// @param id: The ERC721 id of the NFT to bridge to Cadence from EVM /// @param recipient: The Flow account address to receive the bridged NFT /// -transaction(nftContractAddress: Address, nftContractName: String, id: UInt256, recipient: Address) { +transaction(nftIdentifier: String, id: UInt256, recipient: Address) { let nftType: Type let receiver: &{NonFungibleToken.Receiver} @@ -37,12 +35,16 @@ transaction(nftContractAddress: Address, nftContractName: String, id: UInt256, r self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow COA from provided gateway address") - // Get the ERC721 contract address for the given NFT type - self.nftType = FlowEVMBridgeUtils.buildCompositeType( - address: nftContractAddress, - contractName: nftContractName, - resourceName: "NFT" - ) ?? panic("Could not construct NFT type") + /* --- 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 --- */ // 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 418b3bc7..858fc713 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 @@ -2,7 +2,6 @@ import "FungibleToken" import "NonFungibleToken" import "ViewResolver" import "MetadataViews" -import "FlowToken" import "ScopedFTProviders" @@ -17,18 +16,28 @@ import "FlowEVMBridgeUtils" /// 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 nftContractAddress: The Flow account address hosting the NFT-defining Cadence contract -/// @param nftContractName: The name of the NFT-defining Cadence contract +/// @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(nftContractAddress: Address, nftContractName: String, id: UInt64, recipient: String) { +transaction(nftIdentifier: String, id: UInt64, recipient: String) { let nft: @{NonFungibleToken.NFT} let requiresOnboarding: Bool let scopedProvider: @ScopedFTProviders.ScopedFTProvider prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + /* --- Construct the NFT type --- */ + // + // Construct the NFT type from the provided identifier + let 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: nftType) + ?? panic("Could not get contract address from identifier: ".concat(nftIdentifier)) + let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: nftType) + ?? panic("Could not get contract name from identifier: ".concat(nftIdentifier)) + /* --- Retrieve the NFT --- */ // // Borrow a reference to the NFT collection, configuring if necessary diff --git a/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc b/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc index 22eb23b8..2a18426a 100644 --- a/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc +++ b/cadence/transactions/bridge/nft/bridge_nft_to_evm.cdc @@ -17,11 +17,10 @@ import "FlowEVMBridgeUtils" /// 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 nftContractAddress: The Flow account address hosting the NFT-defining Cadence contract -/// @param nftContractName: The name of the NFT-defining Cadence contract +/// @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(nftContractAddress: Address, nftContractName: String, id: UInt64) { +transaction(nftIdentifier: String, id: UInt64) { let nft: @{NonFungibleToken.NFT} let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount @@ -35,13 +34,24 @@ transaction(nftContractAddress: Address, nftContractName: String, id: UInt64) { 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 + let 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: nftType) + ?? panic("Could not get contract address from identifier: ".concat(nftIdentifier)) + let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: 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: nil, + resourceType: nftType, viewType: Type() ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") let collection = signer.storage.borrow( diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc index a92bbced..33e099c1 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc @@ -18,11 +18,11 @@ import "FlowEVMBridgeUtils" /// NOTE: The ERC721 must have first been onboarded to the bridge. This can be checked via the method /// FlowEVMBridge.evmAddressRequiresOnboarding(address: self.evmContractAddress) /// -/// @param tokenContractAddress: The Flow account address hosting the FT-defining Cadence contract -/// @param tokenContractName: The name of the Vault-defining Cadence contract +/// @param vaultIdentifier: The Cadence type identifier of the FungibleToken Vault to bridge +/// - e.g. vault.getType().identifier /// @param amount: The amount of tokens to bridge from EVM /// -transaction(tokenContractAddress: Address, tokenContractName: String, amount: UInt256) { +transaction(vaultIdentifier: String, amount: UInt256) { let vaultType: Type let receiver: &{FungibleToken.Vault} @@ -36,12 +36,16 @@ transaction(tokenContractAddress: Address, tokenContractName: String, amount: UI self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow COA from provided gateway address") - // Get the ERC20 contract address for the given FungibleToken Vault type - self.vaultType = FlowEVMBridgeUtils.buildCompositeType( - address: tokenContractAddress, - contractName: tokenContractName, - resourceName: "Vault" - ) ?? panic("Could not construct Vault type of: " .concat(tokenContractAddress.toString()).concat(".").concat(tokenContractName).concat(".Vault")) + /* --- Construct the Vault type --- */ + // + // Construct the Vault type from the provided identifier + self.vaultType = CompositeType(vaultIdentifier) + ?? panic("Could not construct Vault type from identifier: ".concat(vaultIdentifier)) + // Parse the Vault identifier into its components + let tokenContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.vaultType) + ?? panic("Could not get contract address from identifier: ".concat(vaultIdentifier)) + let tokenContractName = FlowEVMBridgeUtils.getContractName(fromType: self.vaultType) + ?? panic("Could not get contract name from identifier: ".concat(vaultIdentifier)) /* --- Reference the signer's Vault --- */ // 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 e3c0ff06..038e0094 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 @@ -20,12 +20,12 @@ import "FlowEVMBridgeUtils" /// NOTE: The ERC20 must have first been onboarded to the bridge. This can be checked via the method /// FlowEVMBridge.evmAddressRequiresOnboarding(address: self.evmContractAddress) /// -/// @param tokenContractAddress: The Flow account address hosting the FT-defining Cadence contract -/// @param tokenContractName: The name of the Vault-defining Cadence contract -/// @param amount: The amount of tokens to bridge from EVM +/// @param vaultIdentifier: The Cadence type identifier of the FungibleToken Vault to bridge +/// - e.g. vault.getType().identifier +/// @param amount: The amount of tokens to bridge from EVM and transfer to the recipient /// @param recipient: The Flow account address to receive the bridged tokens /// -transaction(tokenContractAddress: Address, tokenContractName: String, amount: UInt256, recipient: Address) { +transaction(vaultIdentifier: String, amount: UInt256, recipient: Address) { let vaultType: Type let receiver: &{FungibleToken.Receiver} @@ -39,12 +39,16 @@ transaction(tokenContractAddress: Address, tokenContractName: String, amount: UI self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow COA from provided gateway address") - // Get the ERC20 contract address for the given FungibleToken Vault type - self.vaultType = FlowEVMBridgeUtils.buildCompositeType( - address: tokenContractAddress, - contractName: tokenContractName, - resourceName: "Vault" - ) ?? panic("Could not construct Vault type of: " .concat(tokenContractAddress.toString()).concat(".").concat(tokenContractName).concat(".Vault")) + /* --- Construct the Vault type --- */ + // + // Construct the Vault type from the provided identifier + self.vaultType = CompositeType(vaultIdentifier) + ?? panic("Could not construct Vault type from identifier: ".concat(vaultIdentifier)) + // Parse the Vault identifier into its components + let tokenContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.vaultType) + ?? panic("Could not get contract address from identifier: ".concat(vaultIdentifier)) + let tokenContractName = FlowEVMBridgeUtils.getContractName(fromType: self.vaultType) + ?? panic("Could not get contract name from identifier: ".concat(vaultIdentifier)) /* --- Reference the signer's Vault --- */ // 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 4a45784d..69c7de0c 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 @@ -18,18 +18,29 @@ import "FlowEVMBridgeConfig" /// NOTE: The Vault being bridged must have first been onboarded to the bridge. This can be checked for with the method /// FlowEVMBridge.typeRequiresOnboarding(type): Bool? /// -/// @param tokenContractAddress: The Flow account address hosting the FT-defining Cadence contract -/// @param tokenContractName: The name of the Vault-defining Cadence contract -/// @param amount: The amount of tokens to bridge from EVM +/// @param vaultIdentifier: The Cadence type identifier of the FungibleToken Vault to bridge +/// - e.g. vault.getType().identifier +/// @param amount: The amount of tokens to bridge from Cadence to the named recipient in EVM /// @param recipient: The hex-encoded EVM address to send the tokens to /// -transaction(tokenContractAddress: Address, tokenContractName: String, amount: UFix64, recipient: String) { +transaction(vaultIdentifier: String, amount: UFix64, recipient: String) { let sentVault: @{FungibleToken.Vault} let requiresOnboarding: Bool let scopedProvider: @ScopedFTProviders.ScopedFTProvider prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + /* --- Construct the Vault type --- */ + // + // Construct the Vault type from the provided identifier + let vaultType = CompositeType(vaultIdentifier) + ?? panic("Could not construct Vault type from identifier: ".concat(vaultIdentifier)) + // Parse the Vault identifier into its components + let tokenContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: vaultType) + ?? panic("Could not get contract address from identifier: ".concat(vaultIdentifier)) + let tokenContractName = FlowEVMBridgeUtils.getContractName(fromType: vaultType) + ?? panic("Could not get contract name from identifier: ".concat(vaultIdentifier)) + /* --- Retrieve the funds --- */ // // Borrow a reference to the FungibleToken Vault diff --git a/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc b/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc index 6ba68244..0938b341 100644 --- a/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc +++ b/cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc @@ -16,11 +16,11 @@ import "FlowEVMBridgeUtils" /// NOTE: This transaction also onboards the Vault to the bridge if necessary which may incur additional fees /// than bridging an asset that has already been onboarded. /// -/// @param tokenContractAddress: The Flow account address hosting the FT-defining Cadence contract -/// @param tokenContractName: The name of the Vault-defining Cadence contract +/// @param vaultIdentifier: The Cadence type identifier of the FungibleToken Vault to bridge +/// - e.g. vault.getType().identifier /// @param amount: The amount of tokens to bridge from EVM /// -transaction(tokenContractAddress: Address, tokenContractName: String, amount: UFix64) { +transaction(vaultIdentifier: String, amount: UFix64) { let sentVault: @{FungibleToken.Vault} let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount @@ -34,13 +34,24 @@ transaction(tokenContractAddress: Address, tokenContractName: String, amount: UF self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow COA from provided gateway address") + /* --- Construct the Vault type --- */ + // + // Construct the Vault type from the provided identifier + let vaultType = CompositeType(vaultIdentifier) + ?? panic("Could not construct Vault type from identifier: ".concat(vaultIdentifier)) + // Parse the Vault identifier into its components + let tokenContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: vaultType) + ?? panic("Could not get contract address from identifier: ".concat(vaultIdentifier)) + let tokenContractName = FlowEVMBridgeUtils.getContractName(fromType: vaultType) + ?? panic("Could not get contract name from identifier: ".concat(vaultIdentifier)) + /* --- Retrieve the funds --- */ // // Borrow a reference to the FungibleToken Vault let viewResolver = getAccount(tokenContractAddress).contracts.borrow<&{ViewResolver}>(name: tokenContractName) ?? panic("Could not borrow ViewResolver from FungibleToken contract") let vaultData = viewResolver.resolveContractView( - resourceType: nil, + resourceType: vaultType, viewType: Type() ) as! FungibleTokenMetadataViews.FTVaultData? ?? panic("Could not resolve FTVaultData view") let vault = signer.storage.borrow(