Smart Contract
FlowEVMBridgeCustomAssociations
A.1e4aa0b87d10b141.FlowEVMBridgeCustomAssociations
1import NonFungibleToken from 0x1d7e57aa55817448
2import CrossVMMetadataViews from 0x1d7e57aa55817448
3import EVM from 0xe467b9dd11fa00df
4
5import FlowEVMBridgeCustomAssociationTypes from 0x1e4aa0b87d10b141
6
7/// The FlowEVMBridgeCustomAssociations is tasked with preserving custom associations between Cadence assets and their
8/// EVM implementations. These associations should be validated before `saveCustomAssociation` is called by
9/// leveraging the interfaces outlined in FLIP-318 (https://github.com/onflow/flips/issues/318) to ensure that the
10/// declared association is valid and that neither implementation is bridge-defined.
11///
12access(all) contract FlowEVMBridgeCustomAssociations {
13
14 /// Stored associations indexed by Cadence Type
15 access(self) let associationsConfig: @{Type: {FlowEVMBridgeCustomAssociationTypes.CustomConfig}}
16 /// Reverse lookup indexed on serialized EVM contract address
17 access(self) let associationsByEVMAddress: {String: Type}
18
19 /// Event emitted whenever a custom association is established
20 access(all) event CustomAssociationEstablished(
21 type: String,
22 evmContractAddress: String,
23 nativeVMRawValue: UInt8,
24 updatedFromBridged: Bool,
25 fulfillmentMinterType: String?,
26 fulfillmentMinterOrigin: Address?,
27 fulfillmentMinterCapID: UInt64?,
28 fulfillmentMinterUUID: UInt64?,
29 configUUID: UInt64
30 )
31
32 /// Retrieves the EVM address associated with the given Cadence Type if it has been registered as a cross-VM asset
33 ///
34 /// @param with: The Cadence Type to query against
35 ///
36 /// @return The EVM address configured as associated with the provided Cadence Type
37 ///
38 access(all)
39 view fun getEVMAddressAssociated(with type: Type): EVM.EVMAddress? {
40 return self.associationsConfig[type]?.getEVMContractAddress() ?? nil
41 }
42
43 /// Retrieves the Cadence Type associated with the given EVM address if it has been registered as a cross-VM asset
44 ///
45 /// @param with: The EVM contract address to query against
46 ///
47 /// @return The Cadence Type configured as associated with the provided EVM address
48 ///
49 access(all)
50 view fun getTypeAssociated(with evmAddress: EVM.EVMAddress): Type? {
51 return self.associationsByEVMAddress[evmAddress.toString()]
52 }
53
54 /// Returns an EVMPointer containing the data at the time of registration
55 ///
56 /// @param forType: The Cadence Type to query against
57 ///
58 /// @return a copy of the EVMPointer view as registered with the bridge
59 ///
60 access(all)
61 fun getEVMPointerAsRegistered(forType: Type): CrossVMMetadataViews.EVMPointer? {
62 if let config = &self.associationsConfig[forType] as &{FlowEVMBridgeCustomAssociationTypes.CustomConfig}? {
63 return CrossVMMetadataViews.EVMPointer(
64 cadenceType: config.getCadenceType(),
65 cadenceContractAddress: config.getCadenceType().address!,
66 evmContractAddress: config.getEVMContractAddress(),
67 nativeVM: config.getNativeVM()
68 )
69 }
70 return nil
71 }
72
73 /// Returns whether the related CustomConfig is currently paused or not. `nil` is returned if a CustomConfig is not
74 /// found for the given Type
75 ///
76 /// @param forType: The Cadence Type for which to retrieve a registered CustomConfig
77 ///
78 /// @return true if the CustomConfig is paused, false if registered and unpaused, nil if unregistered as a custom
79 /// association
80 ///
81 access(all)
82 view fun isCustomConfigPaused(forType: Type): Bool? {
83 return self.borrowNFTCustomConfig(forType: forType)?.isPaused() ?? nil
84 }
85
86 /// Returns metadata about a registered CustomConfig
87 ///
88 /// @param forType: The Cadence Type of the registered cross-VM asset
89 ///
90 /// @return The CustomConfigInfo struct if the type is registered, nil otherwise
91 ///
92 access(all)
93 fun getCustomConfigInfo(forType: Type): FlowEVMBridgeCustomAssociationTypes.CustomConfigInfo? {
94 if let config = self.borrowNFTCustomConfig(forType: forType) {
95 let fulfillmentMinterType = config.checkFulfillmentMinter() == true ? config.borrowFulfillmentMinter().getType() : nil
96 return FlowEVMBridgeCustomAssociationTypes.CustomConfigInfo(
97 updatedFromBridged: config.isUpdatedFromBridged(),
98 isPaused: config.isPaused(),
99 fulfillmentMinterType: fulfillmentMinterType,
100 evmPointer: self.getEVMPointerAsRegistered(forType: forType)!
101 )
102 }
103 return nil
104 }
105
106 /// Allows the bridge contracts to preserve a custom association. Will revert if a custom association already exists
107 ///
108 /// @param type: The Cadence Type of the associated asset.
109 /// @param evmContractAddress: The EVM address defining the EVM implementation of the associated asset.
110 /// @param nativeVM: The VM in which the asset is distributed by the project. The bridge will mint/escrow in the non-native
111 /// VM environment.
112 /// @param updatedFromBridged: Whether the asset was originally onboarded to the bridge via permissionless
113 /// onboarding. In other words, whether there was first a bridge-defined implementation of the underlying asset.
114 /// @param fulfillmentMinter: An authorized Capability allowing the bridge to fulfill bridge requests moving the
115 /// underlying asset from EVM. Required if the asset is EVM-native.
116 ///
117 access(account)
118 fun saveCustomAssociation(
119 type: Type,
120 evmContractAddress: EVM.EVMAddress,
121 nativeVM: CrossVMMetadataViews.VM,
122 updatedFromBridged: Bool,
123 fulfillmentMinter: Capability<auth(FlowEVMBridgeCustomAssociationTypes.FulfillFromEVM) &{FlowEVMBridgeCustomAssociationTypes.NFTFulfillmentMinter}>?
124 ) {
125 pre {
126 self.associationsConfig[type] == nil:
127 "Type \(type.identifier) already has a custom association with \(self.borrowNFTCustomConfig(forType: type)!.getEVMContractAddress().toString())"
128 type.isSubtype(of: Type<@{NonFungibleToken.NFT}>()):
129 "Only NFT cross-VM associations are currently supported but \(type.identifier) is not an NFT implementation"
130 self.associationsByEVMAddress[evmContractAddress.toString()] == nil:
131 "EVM Address \(evmContractAddress.toString()) already has a custom association with \(self.borrowNFTCustomConfig(forType: type)!.getCadenceType().identifier)"
132 fulfillmentMinter?.check() ?? true:
133 "The NFTFulfillmentMinter Capability issued from \(fulfillmentMinter!.address.toString()) is invalid. Ensure the Capability is properly issued and active."
134 }
135 let config <- FlowEVMBridgeCustomAssociationTypes.createNFTCustomConfig(
136 type: type,
137 evmContractAddress: evmContractAddress,
138 nativeVM: nativeVM,
139 updatedFromBridged: updatedFromBridged,
140 fulfillmentMinter: fulfillmentMinter
141 )
142 emit CustomAssociationEstablished(
143 type: type.identifier,
144 evmContractAddress: evmContractAddress.toString(),
145 nativeVMRawValue: nativeVM.rawValue,
146 updatedFromBridged: updatedFromBridged,
147 fulfillmentMinterType: fulfillmentMinter != nil ? fulfillmentMinter!.borrow()!.getType().identifier : nil,
148 fulfillmentMinterOrigin: fulfillmentMinter?.address ?? nil,
149 fulfillmentMinterCapID: fulfillmentMinter?.id ?? nil,
150 fulfillmentMinterUUID: fulfillmentMinter != nil ? fulfillmentMinter!.borrow()!.uuid : nil,
151 configUUID: config.uuid
152 )
153 self.associationsByEVMAddress[config.evmContractAddress.toString()] = type
154 self.associationsConfig[type] <-! config
155 }
156
157 /// Allows bridge contracts to fulfill NFT bridging requests for EVM-native NFTs, using the provided
158 /// NFTFulfillmentMinter Capability provided by the project on cross-VM registration to mint a new NFT.
159 /// **NOTE:** Given the bridge's mint/escrow pattern for the non-native VM, any calls should first check that the
160 /// requested NFT is not locked in escrow before minting.
161 ///
162 /// @param forType: The Cadence Type of the NFT being fulfilled
163 /// @param id: The ERC721 ID of the requested NFT
164 ///
165 /// @param
166 access(account)
167 fun fulfillNFTFromEVM(forType: Type, id: UInt256): @{NonFungibleToken.NFT} {
168 post {
169 result.getType() == forType:
170 "Requested \(forType.identifier) but got \(result.getType().identifier) on fulfillment from EVM"
171 }
172 let config = self.borrowNFTCustomConfig(forType: forType)
173 ?? panic("No CustomConfig found for type \(forType.identifier) - cannot fulfill NFT \(id) from EVM")
174 let minter = config.borrowFulfillmentMinter()
175 return <- minter.fulfillFromEVM(id: id)
176 }
177
178 /// Sets the associated CustomConfig as paused, preventing bridging operations on the associated implementations.
179 /// Expect a no-op in the event the CustomConfig is already paused
180 ///
181 access(account) fun pauseCustomConfig(forType: Type) {
182 let config = self.borrowNFTCustomConfig(forType: forType)
183 ?? panic("No CustomConfig found for type \(forType.identifier) - cannot pause config that does not exist")
184 if !config.isPaused() {
185 config.setPauseStatus(true)
186 }
187 }
188
189 /// Sets the associated CustomConfig as unpaused, preventing bridging operations on the associated implementations.
190 /// Expect a no-op in the event the CustomConfig is already paused
191 ///
192 access(account) fun unpauseCustomConfig(forType: Type) {
193 let config = self.borrowNFTCustomConfig(forType: forType)
194 ?? panic("No CustomConfig found for type \(forType.identifier) - cannot unpause config that does not exist")
195 if config.isPaused() {
196 config.setPauseStatus(false)
197 }
198 }
199
200 /// Returns a reference to the NFTCustomConfig if it exists, nil otherwise
201 ///
202 access(self) view fun borrowNFTCustomConfig(forType: Type): &FlowEVMBridgeCustomAssociationTypes.NFTCustomConfig? {
203 let config = &self.associationsConfig[forType] as &{FlowEVMBridgeCustomAssociationTypes.CustomConfig}?
204 return config as? &FlowEVMBridgeCustomAssociationTypes.NFTCustomConfig
205 }
206
207
208 init() {
209 self.associationsConfig <- {}
210 self.associationsByEVMAddress = {}
211 }
212}
213