-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add cross VM txfr to Cadence txns + tests
- Loading branch information
1 parent
836696e
commit c8c4fd0
Showing
5 changed files
with
370 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
cadence/transactions/bridge/nft/bridge_nft_to_any_cadence_address.cdc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>(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<MetadataViews.NFTCollectionData>() | ||
) 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<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>( | ||
/storage/flowTokenVault | ||
) | ||
signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) | ||
} | ||
// Copy the stored Provider capability and create a ScopedFTProvider | ||
let providerCapCopy = signer.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>>( | ||
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 | ||
} | ||
} |
109 changes: 109 additions & 0 deletions
109
cadence/transactions/bridge/tokens/bridge_tokens_to_any_cadence_address.cdc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>(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<FungibleTokenMetadataViews.FTVaultData>() | ||
) 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<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>( | ||
/storage/flowTokenVault | ||
) | ||
signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) | ||
} | ||
// Copy the stored Provider capability and create a ScopedFTProvider | ||
let providerCapCopy = signer.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>>( | ||
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 | ||
} | ||
} |
Oops, something went wrong.