Skip to content

Commit

Permalink
Merge pull request #133 from onflow/gio/update-fee-estimate
Browse files Browse the repository at this point in the history
Update fee estimate
  • Loading branch information
sisyphusSmiling authored Oct 29, 2024
2 parents d346978 + d043d98 commit 1ca51a6
Show file tree
Hide file tree
Showing 13 changed files with 748 additions and 115 deletions.
175 changes: 136 additions & 39 deletions cadence/tests/flow_evm_bridge_tests.cdc

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions cadence/transactions/bridge/nft/batch_bridge_nft_from_evm.cdc
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
}
}
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
}
}
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
}
}
Loading

0 comments on commit 1ca51a6

Please sign in to comment.