-
Notifications
You must be signed in to change notification settings - Fork 7
/
bridge_tokens_from_evm.cdc
122 lines (109 loc) · 6.23 KB
/
bridge_tokens_from_evm.cdc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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.
///
/// NOTE: The ERC20 must have first been onboarded to the bridge. This can be checked via the method
/// FlowEVMBridge.evmAddressRequiresOnboarding(address: self.evmContractAddress)
///
/// @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(vaultIdentifier: String, amount: UInt256) {
let vaultType: Type
let receiver: &{FungibleToken.Vault}
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 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 --- */
//
// 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 with name"
.concat(tokenContractName).concat(" and address ")
.concat(tokenContractAddress.toString()))
let vaultData = viewResolver.resolveContractView(
resourceType: self.vaultType,
viewType: Type<FungibleTokenMetadataViews.FTVaultData>()
) as! FungibleTokenMetadataViews.FTVaultData?
?? panic("Could not resolve FTVaultData view for Vault type ".concat(self.vaultType.identifier))
// If the vault does not exist, create it and publish according to the contract's defined configuration
if signer.storage.borrow<&{FungibleToken.Vault}>(from: vaultData.storagePath) == nil {
signer.storage.save(<-vaultData.createEmptyVault(), to: vaultData.storagePath)
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 = signer.storage.borrow<&{FungibleToken.Vault}>(from: vaultData.storagePath)
?? panic("Could not borrow FungibleToken Vault from storage path ".concat(vaultData.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
)
// 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 {
// 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}
)
// Ensure the bridged vault is the correct type
assert(
vault.getType() == self.vaultType,
message: "Bridged vault type mismatch - requested: ".concat(self.vaultType.identifier)
.concat(", received: ").concat(vault.getType().identifier)
)
// Deposit the bridged token into the signer's vault
self.receiver.deposit(from: <-vault)
// Destroy the ScopedFTProvider
destroy self.scopedProvider
}
}