-
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.
Merge pull request #133 from onflow/gio/update-fee-estimate
Update fee estimate
- Loading branch information
Showing
13 changed files
with
748 additions
and
115 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
116 changes: 116 additions & 0 deletions
116
cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.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,116 @@ | ||
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<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>(from: /storage/evm) | ||
?? panic("Could not borrow COA signer's account at path /storage/evm") | ||
|
||
/* --- 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 with name " | ||
.concat(nftContractName).concat(" and address ") | ||
.concat(nftContractAddress.toString())) | ||
let collectionData = viewResolver.resolveContractView( | ||
resourceType: self.nftType, | ||
viewType: Type<MetadataViews.NFTCollectionData>() | ||
) 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) | ||
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 a NonFungibleToken Collection from the signer's storage path " | ||
.concat(collectionData.storagePath.toString())) | ||
|
||
/* --- Configure a ScopedFTProvider --- */ | ||
// | ||
// Set a cap on the withdrawable bridge fee | ||
var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( | ||
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 { | ||
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 FungibleToken Provider Capability found in storage at path " | ||
.concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) | ||
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 - requested: ".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 | ||
} | ||
} |
118 changes: 118 additions & 0 deletions
118
cadence/transactions/bridge/nft/batch_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,118 @@ | ||
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<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>(from: /storage/evm) | ||
?? panic("Could not borrow COA signer's account at path /storage/evm") | ||
|
||
/* --- 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 with name " | ||
.concat(nftContractName).concat(" and address ") | ||
.concat(nftContractAddress.toString())) | ||
let collectionData = viewResolver.resolveContractView( | ||
resourceType: self.nftType, | ||
viewType: Type<MetadataViews.NFTCollectionData>() | ||
) 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) | ||
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 NonFungibleToken Receiver from recipient's public capability path") | ||
|
||
/* --- Configure a ScopedFTProvider --- */ | ||
// | ||
// Set a cap on the withdrawable bridge fee | ||
var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( | ||
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 { | ||
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 FungibleToken Provider Capability found in storage at path " | ||
.concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) | ||
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 - requested: ".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 | ||
} | ||
} |
131 changes: 131 additions & 0 deletions
131
cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_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,131 @@ | ||
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<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>(from: /storage/evm) | ||
?? panic("Could not borrow COA signer's account at path /storage/evm") | ||
|
||
/* --- 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 with name " | ||
.concat(nftContractName).concat(" and address ") | ||
.concat(nftContractAddress.toString())) | ||
let collectionData = viewResolver.resolveContractView( | ||
resourceType: self.nftType, | ||
viewType: Type<MetadataViews.NFTCollectionData>() | ||
) as! MetadataViews.NFTCollectionData? | ||
?? panic("Could not resolve NFTCollectionData view for NFT type ".concat(self.nftType.identifier)) | ||
self.collection = signer.storage.borrow<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>( | ||
from: collectionData.storagePath | ||
) ?? 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( | ||
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) | ||
?? 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 | ||
} | ||
|
||
/* --- 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<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 FungibleToken Provider Capability found in storage at path " | ||
.concat(FlowEVMBridgeConfig.providerCapabilityStoragePath.toString())) | ||
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} | ||
) | ||
} | ||
|
||
// 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) | ||
) | ||
// 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 | ||
} | ||
} |
Oops, something went wrong.