From 7446fe86918d661e90f1e6da5e4e7d17f5ff69e4 Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:47:25 -0400 Subject: [PATCH 1/2] chore: target versions of contracts (main branch) for upgrade review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This represents the versions to be upgraded to from mainnet. DO NOT MERGE — this is a review-only branch showing mainnet → main diff. --- cadence/contracts/bridge/FlowEVMBridge.cdc | 182 ++++--- .../bridge/FlowEVMBridgeAccessor.cdc | 51 +- .../contracts/bridge/FlowEVMBridgeConfig.cdc | 18 +- .../FlowEVMBridgeCustomAssociationTypes.cdc | 8 +- .../FlowEVMBridgeCustomAssociations.cdc | 10 +- .../bridge/FlowEVMBridgeHandlers.cdc | 80 ++- .../bridge/FlowEVMBridgeNFTEscrow.cdc | 18 +- .../bridge/FlowEVMBridgeResolver.cdc | 18 +- .../bridge/FlowEVMBridgeTemplates.cdc | 8 +- .../bridge/FlowEVMBridgeTokenEscrow.cdc | 24 +- .../contracts/bridge/FlowEVMBridgeUtils.cdc | 497 +++++++++++------- .../bridge/interfaces/CrossVMNFT.cdc | 10 +- .../bridge/interfaces/CrossVMToken.cdc | 4 +- .../FlowEVMBridgeHandlerInterfaces.cdc | 22 +- .../contracts/bridge/interfaces/ICrossVM.cdc | 2 +- .../bridge/interfaces/ICrossVMAsset.cdc | 4 +- .../bridge/interfaces/IEVMBridgeNFTMinter.cdc | 2 +- .../interfaces/IEVMBridgeTokenMinter.cdc | 2 +- .../bridge/interfaces/IFlowEVMNFTBridge.cdc | 15 +- .../bridge/interfaces/IFlowEVMTokenBridge.cdc | 11 +- .../contracts/example-assets/ExampleNFT.cdc | 6 +- .../contracts/example-assets/ExampleToken.cdc | 6 +- cadence/contracts/handled-tokens/USDCFlow.cdc | 63 ++- cadence/contracts/utils/ScopedFTProviders.cdc | 4 +- cadence/contracts/utils/Serialize.cdc | 6 +- cadence/contracts/utils/SerializeMetadata.cdc | 10 +- cadence/contracts/utils/StringUtils.cdc | 2 +- 27 files changed, 603 insertions(+), 480 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridge.cdc b/cadence/contracts/bridge/FlowEVMBridge.cdc index 7a8b8287..7c321670 100644 --- a/cadence/contracts/bridge/FlowEVMBridge.cdc +++ b/cadence/contracts/bridge/FlowEVMBridge.cdc @@ -1,30 +1,30 @@ -import Burner from 0xf233dcee88fe0abe -import FungibleToken from 0xf233dcee88fe0abe -import FungibleTokenMetadataViews from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 -import MetadataViews from 0x1d7e57aa55817448 -import CrossVMMetadataViews from 0x1d7e57aa55817448 -import ViewResolver from 0x1d7e57aa55817448 - -import EVM from 0xe467b9dd11fa00df - -import IBridgePermissions from 0x1e4aa0b87d10b141 -import ICrossVM from 0x1e4aa0b87d10b141 -import IEVMBridgeNFTMinter from 0x1e4aa0b87d10b141 -import IEVMBridgeTokenMinter from 0x1e4aa0b87d10b141 -import IFlowEVMNFTBridge from 0x1e4aa0b87d10b141 -import IFlowEVMTokenBridge from 0x1e4aa0b87d10b141 -import CrossVMNFT from 0x1e4aa0b87d10b141 -import CrossVMToken from 0x1e4aa0b87d10b141 -import FlowEVMBridgeCustomAssociationTypes from 0x1e4aa0b87d10b141 -import FlowEVMBridgeCustomAssociations from 0x1e4aa0b87d10b141 -import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 -import FlowEVMBridgeHandlerInterfaces from 0x1e4aa0b87d10b141 -import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 -import FlowEVMBridgeNFTEscrow from 0x1e4aa0b87d10b141 -import FlowEVMBridgeTokenEscrow from 0x1e4aa0b87d10b141 -import FlowEVMBridgeTemplates from 0x1e4aa0b87d10b141 -import SerializeMetadata from 0x1e4aa0b87d10b141 +import "Burner" +import "FungibleToken" +import "FungibleTokenMetadataViews" +import "NonFungibleToken" +import "MetadataViews" +import "CrossVMMetadataViews" +import "ViewResolver" + +import "EVM" + +import "IBridgePermissions" +import "ICrossVM" +import "IEVMBridgeNFTMinter" +import "IEVMBridgeTokenMinter" +import "IFlowEVMNFTBridge" +import "IFlowEVMTokenBridge" +import "CrossVMNFT" +import "CrossVMToken" +import "FlowEVMBridgeCustomAssociationTypes" +import "FlowEVMBridgeCustomAssociations" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeHandlerInterfaces" +import "FlowEVMBridgeUtils" +import "FlowEVMBridgeNFTEscrow" +import "FlowEVMBridgeTokenEscrow" +import "FlowEVMBridgeTemplates" +import "SerializeMetadata" /// The FlowEVMBridge contract is the main entrypoint for bridging NFT & FT assets between Flow & FlowEVM. /// @@ -75,10 +75,10 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { pre { !FlowEVMBridgeConfig.isPaused(): "Bridge operations are currently paused" !FlowEVMBridgeConfig.isCadenceTypeBlocked(type): - "This Cadence Type ".concat(type.identifier).concat(" is currently blocked from being onboarded") + "This Cadence Type \(type.identifier) is currently blocked from being onboarded" self.typeRequiresOnboarding(type) == true: "Onboarding is not needed for this type" FlowEVMBridgeUtils.typeAllowsBridging(type): - "This Cadence Type ".concat(type.identifier).concat(" is currently opted-out of bridge onboarding") + "This Cadence Type \(type.identifier) is currently opted-out of bridge onboarding" FlowEVMBridgeUtils.isCadenceNative(type: type): "Only Cadence-native assets can be onboarded by Type" } /* Custom cross-VM Implementation check */ @@ -115,7 +115,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { let vault <-createVaultFunction(type) assert( vault.getType() == type, - message: "Requested to onboard type=".concat(type.identifier).concat( "but contract returned type=").concat(vault.getType().identifier) + message: "Requested to onboard type=\(type.identifier) but contract returned type=\(vault.getType().identifier)" ) FlowEVMBridgeTokenEscrow.initializeEscrow( with: <-vault, @@ -125,7 +125,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { evmTokenAddress: onboardingValues.evmContractAddress ) } else { - panic("Attempted to onboard unsupported type: ".concat(type.identifier)) + panic("Attempted to onboard unsupported type: \(type.identifier)") } /* Confirmation */ @@ -157,7 +157,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { pre { !FlowEVMBridgeConfig.isPaused(): "Bridge operations are currently paused" !FlowEVMBridgeConfig.isEVMAddressBlocked(address): - "This EVM contract ".concat(address.toString()).concat(" is currently blocked from being onboarded") + "This EVM contract \(address.toString()) is currently blocked from being onboarded" } /* Custom cross-VM Implementation check */ // @@ -216,18 +216,13 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { !FlowEVMBridgeConfig.isCadenceTypeBlocked(type): "Type \(type.identifier) has been blocked from onboarding" FlowEVMBridgeUtils.isCadenceNative(type: type): - "Attempting to register a bridge-deployed NFT - cannot update a bridge-defined asset. If updating your EVM " - .concat("contract's Cadence association, deploy your Cadence NFT contract and register using the newly defined Cadence type") + "Attempting to register a bridge-deployed NFT - cannot update a bridge-defined asset. If updating your EVM contract's Cadence association, deploy your Cadence NFT contract and register using the newly defined Cadence type" FlowEVMBridgeCustomAssociations.getEVMAddressAssociated(with: type) == nil: - "A custom association has already been declared for type \(type.identifier) with EVM address " - .concat(FlowEVMBridgeCustomAssociations.getEVMAddressAssociated(with: type)!.toString()) - .concat(". Custom associations can only be declared once for any given Cadence Type or EVM contract") + "A custom association has already been declared for type \(type.identifier) with EVM address \(FlowEVMBridgeCustomAssociations.getEVMAddressAssociated(with: type)!.toString()). Custom associations can only be declared once for any given Cadence Type or EVM contract" fulfillmentMinter?.check() ?? true: - "NFTFulfillmentMinter Capability is invalid - Issue a new " - .concat("Capability and try again") + "NFTFulfillmentMinter Capability is invalid - Issue a new Capability and try again" fulfillmentMinter != nil ? fulfillmentMinter!.borrow()!.getType().address! == type.address! : true: - "NFTFulfillmentMinter must be defined by a contract deployed to the registered type address \(type.address!) " - .concat(" but found defining address of \(fulfillmentMinter!.borrow()!.getType().address!)") + "NFTFulfillmentMinter must be defined by a contract deployed to the registered type address \(type.address!) but found defining address of \(fulfillmentMinter!.borrow()!.getType().address!)" } /* Provision fees */ // @@ -248,15 +243,12 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { ) assert( FlowEVMBridgeCustomAssociations.getTypeAssociated(with: evmPointer.evmContractAddress) == nil, - message: "A custom association has already been declared for EVM address \(evmPointer.evmContractAddress.toString()) with Cadence Type " - .concat(FlowEVMBridgeCustomAssociations.getTypeAssociated(with: evmPointer.evmContractAddress)?.identifier ?? "") - .concat(". Custom associations can only be declared once for any given Cadence Type or EVM contract") + message: "A custom association has already been declared for EVM address \(evmPointer.evmContractAddress.toString()) with Cadence Type \(FlowEVMBridgeCustomAssociations.getTypeAssociated(with: evmPointer.evmContractAddress)?.identifier ?? ""). Custom associations can only be declared once for any given Cadence Type or EVM contract" ) assert( FlowEVMBridgeUtils.isERC721(evmContractAddress: evmPointer.evmContractAddress) && !FlowEVMBridgeUtils.isERC20(evmContractAddress: evmPointer.evmContractAddress), - message: "Cross-VM NFTs must be implemented as ERC721 exclusively, but detected an invalid EVM interface " - .concat("at EVM contract \(evmPointer.evmContractAddress.toString())") + message: "Cross-VM NFTs must be implemented as ERC721 exclusively, but detected an invalid EVM interface at EVM contract \(evmPointer.evmContractAddress.toString())" ) // Get pointer on EVM side @@ -282,15 +274,11 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { // Check supportsInterface() for CrossVMBridgeERC721Fulfillment if NFT is Cadence-native if evmPointer.nativeVM == CrossVMMetadataViews.VM.Cadence { assert(FlowEVMBridgeUtils.supportsCadenceNativeNFTEVMInterfaces(evmContract: evmPointer.evmContractAddress), - message: "Corresponding EVM contract does not implement necessary EVM interfaces ICrossVMBridgeERC721Fulfillment " - .concat("and/or ICrossVMBridgeCallable. All Cadence-native cross-VM NFTs must implement these interfaces and ") - .concat("grant the bridge COA the ability to fulfill bridge requests moving NFTs into EVM.")) + message: "Corresponding EVM contract does not implement necessary EVM interfaces ICrossVMBridgeERC721Fulfillment and/or ICrossVMBridgeCallable. All Cadence-native cross-VM NFTs must implement these interfaces and grant the bridge COA the ability to fulfill bridge requests moving NFTs into EVM.") let designatedVMBridgeAddress = FlowEVMBridgeUtils.getVMBridgeAddressFromICrossVMBridgeCallable(evmContract: evmPointer.evmContractAddress) - ?? panic("Could not recover declared VM bridge address from EVM contract \(evmPointer.evmContractAddress.toString()). " - .concat("Ensure the contract conforms to ICrossVMBridgeCallable and declare the vmBridgeAddress as \(FlowEVMBridgeUtils.getBridgeCOAEVMAddress().toString())")) + ?? panic("Could not recover declared VM bridge address from EVM contract \(evmPointer.evmContractAddress.toString()). Ensure the contract conforms to ICrossVMBridgeCallable and declare the vmBridgeAddress as \(FlowEVMBridgeUtils.getBridgeCOAEVMAddress().toString())") assert(designatedVMBridgeAddress.equals(FlowEVMBridgeUtils.getBridgeCOAEVMAddress()), - message: "ICrossVMBridgeCallable declared \(designatedVMBridgeAddress.toString())" - .concat(" as vmBridgeAddress which must be declared as \(FlowEVMBridgeUtils.getBridgeCOAEVMAddress().toString())")) + message: "ICrossVMBridgeCallable declared \(designatedVMBridgeAddress.toString()) as vmBridgeAddress which must be declared as \(FlowEVMBridgeUtils.getBridgeCOAEVMAddress().toString())") } /* Native VM consistency check */ @@ -299,21 +287,14 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { let legacyEVMAssoc = FlowEVMBridgeConfig.getLegacyEVMAddressAssociated(with: type) let legacyCadenceAssoc = FlowEVMBridgeConfig.getLegacyTypeAssociated(with: evmPointer.evmContractAddress) assert(legacyEVMAssoc == nil || legacyCadenceAssoc == nil, - message: "Both the EVM contract \(evmPointer.evmContractAddress.toString()) and the Cadence Type \(type.identifier) " - .concat("have already been onboarded to the VM bridge - one side of this association will have to be redeployed ") - .concat("and the declared association updated to a non-onboarded target in order to register as a custom cross-VM asset.")) + message: "Both the EVM contract \(evmPointer.evmContractAddress.toString()) and the Cadence Type \(type.identifier) have already been onboarded to the VM bridge - one side of this association will have to be redeployed and the declared association updated to a non-onboarded target in order to register as a custom cross-VM asset.") // Ensure the native VM is consistent if the NFT has been previously onboarded via the permissionless path if legacyEVMAssoc != nil { assert(evmPointer.nativeVM == CrossVMMetadataViews.VM.Cadence, - message: "Attempting to register NFT \(type.identifier) as EVM-native after it has already been " - .concat("onboarded as Cadence-native. This NFT must be configured as Cadence-native with an ERC721 ") - .concat("implementing CrossVMBridgeERC721Fulfillment base contract allowing the bridge to fulfill ") - .concat("NFTs moving into EVM")) + message: "Attempting to register NFT \(type.identifier) as EVM-native after it has already been onboarded as Cadence-native. This NFT must be configured as Cadence-native with an ERC721 implementing CrossVMBridgeERC721Fulfillment base contract allowing the bridge to fulfill NFTs moving into EVM") } else if legacyCadenceAssoc != nil { assert(evmPointer.nativeVM == CrossVMMetadataViews.VM.EVM, - message: "Attempting to register NFT \(type.identifier) as Cadence-native after it has already been " - .concat("onboarded as EVM-native. This NFT must be configured as EVM-native and provide an NFTFulfillmentMinter ") - .concat("Capability so the bridge may fulfill NFTs moving into Cadence.")) + message: "Attempting to register NFT \(type.identifier) as Cadence-native after it has already been onboarded as EVM-native. This NFT must be configured as EVM-native and provide an NFTFulfillmentMinter Capability so the bridge may fulfill NFTs moving into Cadence.") } FlowEVMBridgeCustomAssociations.saveCustomAssociation( @@ -394,7 +375,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { type: Type, id: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, - protectedTransferCall: fun (EVM.EVMAddress): EVM.Result + protectedTransferCall: fun (EVM.EVMAddress): EVM.ResultDecoded ): @{NonFungibleToken.NFT} { pre { !FlowEVMBridgeConfig.isPaused(): "Bridge operations are currently paused" @@ -406,7 +387,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { let customAssocByType = FlowEVMBridgeCustomAssociations.getEVMAddressAssociated(with: type) let customAssocByEVMAddr = bridgedAssoc != nil ? FlowEVMBridgeCustomAssociations.getTypeAssociated(with: bridgedAssoc!) : nil // Initialize the internal handler method that will be used to move the NFT from EVM - var handler: (fun (EVM.EVMAddress, Type, UInt256, auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, fun (EVM.EVMAddress): EVM.Result): @{NonFungibleToken.NFT})? = nil + var handler: (fun (EVM.EVMAddress, Type, UInt256, auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, fun (EVM.EVMAddress): EVM.ResultDecoded): @{NonFungibleToken.NFT})? = nil if bridgedAssoc != nil && customAssocByType == nil && customAssocByEVMAddr == nil { // Common case - bridge-defined counterpart in non-native VM handler = self.handleDefaultNFTFromEVM @@ -532,7 +513,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { type: Type, amount: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, - protectedTransferCall: fun (): EVM.Result + protectedTransferCall: fun (): EVM.ResultDecoded ): @{FungibleToken.Vault} { pre { !FlowEVMBridgeConfig.isPaused(): "Bridge operations are currently paused" @@ -598,12 +579,13 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { message: "Unexpected error bridging FT from EVM" ) // Burn the EVM tokens that have now been transferred to the bridge in EVM - let burnResult: EVM.Result = FlowEVMBridgeUtils.call( + let burnResult = FlowEVMBridgeUtils.callWithSigAndArgs( signature: "burn(uint256)", targetEVMAddress: associatedAddress, args: [amount], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert(burnResult.status == EVM.Status.successful, message: "Burn of EVM tokens failed") @@ -842,8 +824,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { to: EVM.EVMAddress, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}) { let evmPointer = FlowEVMBridgeCustomAssociations.getEVMPointerAsRegistered(forType: token.getType()) - ?? panic("Could not find custom association for cross-VM NFT \(token.getType().identifier) with id \(token.id). " - .concat("Ensure this NFT has been registered as a cross-VM.")) + ?? panic("Could not find custom association for cross-VM NFT \(token.getType().identifier) with id \(token.id). Ensure this NFT has been registered as a cross-VM.") return evmPointer.nativeVM == CrossVMMetadataViews.VM.Cadence ? self.handleCadenceNativeCrossVMNFTToEVM(token: <-token, to: to, feeProvider: feeProvider) : self.handleEVMNativeCrossVMNFTToEVM(token: <-token, to: to, feeProvider: feeProvider) @@ -917,9 +898,17 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { } let bridgedAssociation = FlowEVMBridgeConfig.getLegacyEVMAddressAssociated(with: token.getType())! let updatedCadenceAssociation = FlowEVMBridgeCustomAssociations.getTypeAssociated(with: bridgedAssociation) - ?? panic("Could not find a custom cross-VM association for NFT \(token.getType().identifier) #\(token.id). " - .concat("The handleUpdatedBridgedNFTToEVM route is intended for bridged Cadence NFTs associated with ") - .concat(" ERC721 contracts that have registered as a custom cross-VM NFT collection.")) + ?? panic("Could not find a custom cross-VM association for NFT \(token.getType().identifier) #\(token.id). The handleUpdatedBridgedNFTToEVM route is intended for bridged Cadence NFTs associated with ERC721 contracts that have registered as a custom cross-VM NFT collection.") + + // Ensure the updated/custom type is not paused - the top-level pause check only covers the + // caller-supplied bridge-defined type, so we must re-check here after resolving the migration target + assert(FlowEVMBridgeConfig.isTypePaused(updatedCadenceAssociation) == false, + message: "Bridging is currently paused for type \(updatedCadenceAssociation.identifier)") + + // The force-casts to CrossVMNFT.EVMNFT below are safe: this handler is only reached when the token is a + // bridge-defined NFT (pre-condition enforces !isCadenceNative). All bridge-defined NFTs are deployed from + // the bridge's template contract, which unconditionally implements CrossVMNFT.EVMNFT. No user-controlled + // contract can produce a bridge-defined type, so these casts cannot fail in practice. let tokenRef = (&token as &{NonFungibleToken.NFT}) as! &{CrossVMNFT.EVMNFT} let evmID = tokenRef.evmID let bridgedToken <- token as! @{CrossVMNFT.EVMNFT} @@ -931,6 +920,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { erc721Address: bridgedAssociation.toString() ) Burner.burn(<-bridgedToken) + // No bridge fee is charged here. The bridge burns the legacy Cadence NFT and releases the ERC721 from + // EVM-side escrow — no new assets are added to bridge storage, so no storage cost is incurred. // Transfer the ERC721 from escrow to the named recipient FlowEVMBridgeUtils.mustSafeTransferERC721(erc721Address: bridgedAssociation, to: to, id: evmID) } @@ -943,7 +934,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { type: Type, id: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, - protectedTransferCall: fun (EVM.EVMAddress): EVM.Result + protectedTransferCall: fun (EVM.EVMAddress): EVM.ResultDecoded ): @{NonFungibleToken.NFT} { /* Provision fee */ // @@ -1005,11 +996,10 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { type: Type, id: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, - protectedTransferCall: fun (EVM.EVMAddress): EVM.Result + protectedTransferCall: fun (EVM.EVMAddress): EVM.ResultDecoded ): @{NonFungibleToken.NFT} { let evmPointer = FlowEVMBridgeCustomAssociations.getEVMPointerAsRegistered(forType: type) - ?? panic("Could not find custom association for cross-VM NFT \(type.identifier) with id \(id). " - .concat("Ensure this NFT has been registered as a cross-VM.")) + ?? panic("Could not find custom association for cross-VM NFT \(type.identifier) with id \(id). Ensure this NFT has been registered as a cross-VM.") if evmPointer.nativeVM == CrossVMMetadataViews.VM.Cadence { return <- self.handleCadenceNativeCrossVMNFTFromEVM( owner: owner, @@ -1037,7 +1027,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { type: Type, id: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, - protectedTransferCall: fun (EVM.EVMAddress): EVM.Result + protectedTransferCall: fun (EVM.EVMAddress): EVM.ResultDecoded ): @{NonFungibleToken.NFT} { pre { FlowEVMBridgeUtils.isCadenceNative(type: type): @@ -1071,9 +1061,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { ) } else { // Bridged token not wrapped nor owned by caller - could not determine owner - panic("Bridged ERC721 \(bridgedAssociation!.toString()) ID \(id) still exists after \(type.identifier) " - .concat("was updated to associate with ERC721 \(customERC721.toString()), but the bridged token is ") - .concat("neither wrapped nor owned by caller \(owner.toString()). Could not determine owner.")) + panic("Bridged ERC721 \(bridgedAssociation!.toString()) ID \(id) still exists after \(type.identifier) was updated to associate with ERC721 \(customERC721.toString()), but the bridged token is neither wrapped nor owned by caller \(owner.toString()). Could not determine owner.") } // Burn the bridged ERC721, taking the bridged representation out of circulation in favor of custom ERC721 FlowEVMBridgeUtils.mustBurnERC721(erc721Address: bridgedAssociation!, id: id) @@ -1085,22 +1073,30 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { protectedTransferCall: protectedTransferCall ) } + // No bridge fee is charged here. The bridge is releasing a Cadence-native NFT from existing escrow — the + // storage cost was already accounted for when the NFT was escrowed on the ToEVM path. Unlocking reduces + // bridge storage rather than increasing it, so no new fee is warranted. // Cadence-native NFTs must be in escrow, so unlock & return - return <-FlowEVMBridgeNFTEscrow.unlockNFT( - type: type, - id: FlowEVMBridgeNFTEscrow.getLockedCadenceID(type: type, evmID: id)! - ) + let lockedCadenceID = FlowEVMBridgeNFTEscrow.getLockedCadenceID(type: type, evmID: id) + ?? panic("NFT of type \(type.identifier) with EVM ID \(id) is not in escrow — cannot bridge from EVM") + return <-FlowEVMBridgeNFTEscrow.unlockNFT(type: type, id: lockedCadenceID) } /// Handler to move registered cross-VM EVM-native NFTs from EVM /// + /// Note on `_type` vs `type`: this handler is only reached via `handleCrossVMNFTFromEVM`, which is dispatched + /// when `customAssocByType != nil` — meaning the caller-supplied `type` is always the custom cross-VM type, + /// not a legacy bridge-defined type. Therefore `isCadenceNative(type)` is always true for this handler, the + /// `if !isCadenceNative` branch below is never entered, and `_type` is never reassigned away from `type`. + /// The `isLocked` check and the `unlockNFT`/`fulfillNFTFromEVM` calls therefore always use the same type. + /// access(self) fun handleEVMNativeCrossVMNFTFromEVM( owner: EVM.EVMAddress, type: Type, id: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, - protectedTransferCall: fun (EVM.EVMAddress): EVM.Result + protectedTransferCall: fun (EVM.EVMAddress): EVM.ResultDecoded ): @{NonFungibleToken.NFT} { pre { id <= UInt256(UInt64.max): @@ -1130,6 +1126,9 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { FlowEVMBridgeUtils.mustEscrowERC721(owner: owner, id: id, erc721Address: erc721Address, protectedTransferCall: protectedTransferCall) + // No bridge fee is charged here. EVM-native NFTs are fulfilled either by unlocking from existing Cadence + // escrow (reducing bridge storage) or by minting via the project-provided NFTFulfillmentMinter (no bridge + // storage involved). In neither case does the bridge account incur a new storage cost. if FlowEVMBridgeNFTEscrow.isLocked(type: type, id: UInt64(id)) { // Unlock the NFT from escrow return <-FlowEVMBridgeNFTEscrow.unlockNFT(type: _type, id: UInt64(id)) @@ -1145,7 +1144,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { type: Type, id: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, - protectedTransferCall: fun (EVM.EVMAddress): EVM.Result + protectedTransferCall: fun (EVM.EVMAddress): EVM.ResultDecoded ): @{NonFungibleToken.NFT} { pre { !FlowEVMBridgeUtils.isCadenceNative(type: type): // expect this type to be bridge-defined @@ -1157,6 +1156,11 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { let bridgedAssoc = FlowEVMBridgeConfig.getLegacyEVMAddressAssociated(with: type)! let updatedTypeAssoc = FlowEVMBridgeConfig.getTypeAssociated(with: bridgedAssoc)! + // Ensure the updated/custom type is not paused - the top-level pause check only covers the + // caller-supplied legacy type, so we must re-check here after resolving the migration target + assert(FlowEVMBridgeConfig.isTypePaused(updatedTypeAssoc) == false, + message: "Bridging is currently paused for type \(updatedTypeAssoc.identifier)") + // Confirm custom association is EVM-native let configInfo = FlowEVMBridgeCustomAssociations.getCustomConfigInfo(forType: updatedTypeAssoc)! assert(configInfo.evmPointer.nativeVM == CrossVMMetadataViews.VM.EVM, @@ -1176,6 +1180,10 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { ) Burner.burn(<-bridgedToken) } + // No bridge fee is charged here. The legacy bridge-defined NFT (if present in escrow) is burned, and the + // custom type NFT is either unlocked from existing escrow or minted via the project's NFTFulfillmentMinter. + // In all cases the bridge is releasing or burning assets rather than storing new ones, so no storage cost + // is incurred and no fee is warranted. // Either unlock if locked or fulfill via configured NFTFulfillmentMinter if FlowEVMBridgeNFTEscrow.isLocked(type: updatedTypeAssoc, id: UInt64(id)) { return <- FlowEVMBridgeNFTEscrow.unlockNFT(type: updatedTypeAssoc, id: UInt64(id)) diff --git a/cadence/contracts/bridge/FlowEVMBridgeAccessor.cdc b/cadence/contracts/bridge/FlowEVMBridgeAccessor.cdc index 7532bd76..c6394d8e 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeAccessor.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeAccessor.cdc @@ -1,11 +1,12 @@ -import NonFungibleToken from 0x1d7e57aa55817448 -import FungibleToken from 0xf233dcee88fe0abe -import FlowToken from 0x1654653399040a61 +import "NonFungibleToken" +import "FungibleToken" +import "FlowToken" -import EVM from 0xe467b9dd11fa00df +import "EVM" -import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 -import FlowEVMBridge from 0x1e4aa0b87d10b141 +import "FlowEVMBridgeConfig" +import "FlowEVMBridge" +import "FlowEVMBridgeUtils" /// This contract defines a mechanism for routing bridge requests from the EVM contract to the Flow-EVM bridge contract /// @@ -52,7 +53,7 @@ contract FlowEVMBridgeAccessor { ): @{NonFungibleToken.NFT} { // Define a callback function, enabling the bridge to act on the ephemeral COA reference in scope var executed = false - fun callback(target: EVM.EVMAddress): EVM.Result { + fun callback(target: EVM.EVMAddress): EVM.ResultDecoded { pre { !executed: "Callback can only be executed once" FlowEVMBridge.getAssociatedEVMAddress(with: type) ?? FlowEVMBridgeConfig.getLegacyEVMAddressAssociated(with: type) != nil: @@ -68,14 +69,13 @@ contract FlowEVMBridgeAccessor { message: "Target EVM contract \(target.toString()) is not association with NFT Type \(type.identifier) - COA `safeTransferFrom` callback rejected") executed = true - return caller.call( + return caller.callWithSigAndArgs( to: target, - data: EVM.encodeABIWithSignature( - "safeTransferFrom(address,address,uint256)", - [caller.address(), FlowEVMBridge.getBridgeCOAEVMAddress(), id] - ), + signature: "safeTransferFrom(address,address,uint256)", + args: [caller.address(), FlowEVMBridge.getBridgeCOAEVMAddress(), id], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: EVM.Balance(attoflow: 0) + value: 0, + resultTypes: nil ) } // Execute the bridge request @@ -119,9 +119,16 @@ contract FlowEVMBridgeAccessor { amount: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} ): @{FungibleToken.Vault} { + // Resolve the EVM address associated with the token type + let associatedEVMAddress = FlowEVMBridge.getAssociatedEVMAddress(with: type) + ?? panic("No EVM address associated with type") + // Round the requested ERC20 amount down to the maximum precision representable by UFix64 to ensure the + // amount escrowed on the EVM side matches exactly what will be minted or unlocked on the Cadence side, + // preventing sub-UFix64-precision "dust" from being permanently locked in escrow. + let roundedAmount = FlowEVMBridgeUtils.castERC20AmountToCadencePrecision(amount, erc20Address: associatedEVMAddress) // Define a callback function, enabling the bridge to act on the ephemeral COA reference in scope var executed = false - fun callback(): EVM.Result { + fun callback(): EVM.ResultDecoded { pre { !executed: "Callback can only be executed once" } @@ -129,22 +136,20 @@ contract FlowEVMBridgeAccessor { executed: "Callback must be executed" } executed = true - return caller.call( - to: FlowEVMBridge.getAssociatedEVMAddress(with: type) - ?? panic("No EVM address associated with type"), - data: EVM.encodeABIWithSignature( - "transfer(address,uint256)", - [FlowEVMBridge.getBridgeCOAEVMAddress(), amount] - ), + return caller.callWithSigAndArgs( + to: associatedEVMAddress, + signature: "transfer(address,uint256)", + args: [FlowEVMBridge.getBridgeCOAEVMAddress(), roundedAmount], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: EVM.Balance(attoflow: 0) + value: 0, + resultTypes: nil ) } // Execute the bridge request return <- FlowEVMBridge.bridgeTokensFromEVM( owner: caller.address(), type: type, - amount: amount, + amount: roundedAmount, feeProvider: feeProvider, protectedTransferCall: callback ) diff --git a/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc b/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc index 01eb72c8..534e5cef 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeConfig.cdc @@ -1,8 +1,8 @@ -import EVM from 0xe467b9dd11fa00df -import NonFungibleToken from 0x1d7e57aa55817448 +import "EVM" +import "NonFungibleToken" -import FlowEVMBridgeHandlerInterfaces from 0x1e4aa0b87d10b141 -import FlowEVMBridgeCustomAssociations from 0x1e4aa0b87d10b141 +import "FlowEVMBridgeHandlerInterfaces" +import "FlowEVMBridgeCustomAssociations" /// This contract is used to store configuration information shared by FlowEVMBridge contracts /// @@ -101,8 +101,14 @@ contract FlowEVMBridgeConfig { return self.paused } - /// Returns whether operations for a given Type are paused. A return value of nil indicates the Type is not yet - /// onboarded to the bridge. + /// Returns whether operations for a given Type are paused. + /// + /// Note: the return type is `Bool?` for API compatibility, but this function currently always returns a + /// non-nil `Bool`. The three sub-expressions all produce concrete `Bool` values via nil-coalescing or direct + /// comparison. A `nil` return cannot occur under current logic. Call sites should use `== false` (rather than + /// `!= true`) so that a hypothetical future `nil` is treated conservatively as "paused" rather than + /// "not paused". The doc comment claim that `nil` means "not yet onboarded" does not reflect actual behavior + /// and is retained only for historical reference. /// access(all) view fun isTypePaused(_ type: Type): Bool? { diff --git a/cadence/contracts/bridge/FlowEVMBridgeCustomAssociationTypes.cdc b/cadence/contracts/bridge/FlowEVMBridgeCustomAssociationTypes.cdc index 715833f0..fae6c727 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeCustomAssociationTypes.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeCustomAssociationTypes.cdc @@ -1,7 +1,7 @@ -import FungibleToken from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 -import CrossVMMetadataViews from 0x1d7e57aa55817448 -import EVM from 0xe467b9dd11fa00df +import "FungibleToken" +import "NonFungibleToken" +import "CrossVMMetadataViews" +import "EVM" /// This contract defines types required for custom cross-VM associations as used in FlowEVMBridgeCustomAssociation /// and in EVM-native cross-VM NFTs. diff --git a/cadence/contracts/bridge/FlowEVMBridgeCustomAssociations.cdc b/cadence/contracts/bridge/FlowEVMBridgeCustomAssociations.cdc index 9f735b1b..e7d79221 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeCustomAssociations.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeCustomAssociations.cdc @@ -1,8 +1,8 @@ -import NonFungibleToken from 0x1d7e57aa55817448 -import CrossVMMetadataViews from 0x1d7e57aa55817448 -import EVM from 0xe467b9dd11fa00df +import "NonFungibleToken" +import "CrossVMMetadataViews" +import "EVM" -import FlowEVMBridgeCustomAssociationTypes from 0x1e4aa0b87d10b141 +import "FlowEVMBridgeCustomAssociationTypes" /// The FlowEVMBridgeCustomAssociations is tasked with preserving custom associations between Cadence assets and their /// EVM implementations. These associations should be validated before `saveCustomAssociation` is called by @@ -128,7 +128,7 @@ access(all) contract FlowEVMBridgeCustomAssociations { type.isSubtype(of: Type<@{NonFungibleToken.NFT}>()): "Only NFT cross-VM associations are currently supported but \(type.identifier) is not an NFT implementation" self.associationsByEVMAddress[evmContractAddress.toString()] == nil: - "EVM Address \(evmContractAddress.toString()) already has a custom association with \(self.borrowNFTCustomConfig(forType: type)!.getCadenceType().identifier)" + "EVM Address \(evmContractAddress.toString()) already has a custom association with \(self.associationsByEVMAddress[evmContractAddress.toString()]!.identifier)" fulfillmentMinter?.check() ?? true: "The NFTFulfillmentMinter Capability issued from \(fulfillmentMinter!.address.toString()) is invalid. Ensure the Capability is properly issued and active." } diff --git a/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc b/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc index fb922546..f29f587d 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc @@ -1,13 +1,13 @@ -import Burner from 0xf233dcee88fe0abe -import FungibleToken from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 -import FlowToken from 0x1654653399040a61 +import "Burner" +import "FungibleToken" +import "NonFungibleToken" +import "FlowToken" -import EVM from 0xe467b9dd11fa00df +import "EVM" -import FlowEVMBridgeHandlerInterfaces from 0x1e4aa0b87d10b141 -import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 -import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 +import "FlowEVMBridgeHandlerInterfaces" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" /// FlowEVMBridgeHandlers /// @@ -105,7 +105,7 @@ access(all) contract FlowEVMBridgeHandlers { let amount = tokens.balance let uintAmount = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(amount, erc20Address: evmAddress) - assert(uintAmount > UInt256(0), message: "Amount to bridge must be greater than 0") + assert(uintAmount > 0, message: "Amount to bridge must be greater than 0") Burner.burn(<-tokens) @@ -127,7 +127,7 @@ access(all) contract FlowEVMBridgeHandlers { owner: EVM.EVMAddress, type: Type, amount: UInt256, - protectedTransferCall: fun (): EVM.Result + protectedTransferCall: fun (): EVM.ResultDecoded ): @{FungibleToken.Vault} { let evmAddress = self.getTargetEVMAddress()! @@ -147,7 +147,7 @@ access(all) contract FlowEVMBridgeHandlers { // After state confirmation, mint the tokens and return let minter = self.borrowMinter() - ?? panic("Cannot bridge - Minter not set in ".concat(self.getType().identifier)) + ?? panic("Cannot bridge - Minter not set in \(self.getType().identifier)") let minted <- minter.mint(amount: ufixAmount) return <-minted } @@ -170,7 +170,7 @@ access(all) contract FlowEVMBridgeHandlers { access(FlowEVMBridgeHandlerInterfaces.Admin) fun setMinter(_ minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}) { pre { - self.minter == nil: "Minter has already been set in ".concat(self.getType().identifier) + self.minter == nil: "Minter has already been set in \(self.getType().identifier)" } self.minter <-! minter } @@ -179,7 +179,7 @@ access(all) contract FlowEVMBridgeHandlers { access(FlowEVMBridgeHandlerInterfaces.Admin) fun enableBridging() { pre { - self.minter != nil: "Cannot enable ".concat(self.getType().identifier).concat(" without a minter") + self.minter != nil: "Cannot enable \(self.getType().identifier) without a minter" } self.enabled = true } @@ -262,12 +262,13 @@ access(all) contract FlowEVMBridgeHandlers { let preBalance = FlowEVMBridgeUtils.balanceOf(owner: coa.address(), evmContractAddress: wflowAddress) // Wrap the deposited FLOW as WFLOW, giving the bridge COA the necessary WFLOW to transfer - let wrapResult = FlowEVMBridgeUtils.call( + let wrapResult = FlowEVMBridgeUtils.callWithSigAndArgs( signature: "deposit()", targetEVMAddress: wflowAddress, args: [], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: balance + value: balance, + resultTypes: nil ) assert(wrapResult.status == EVM.Status.successful, message: "Failed to wrap FLOW as WFLOW") @@ -276,16 +277,12 @@ access(all) contract FlowEVMBridgeHandlers { // Cover underflow assert( postBalance > preBalance, - message: "Escrowed WFLOW balance did not increment after wrapping FLOW - pre: " - .concat(preBalance.toString()).concat(" | post: ").concat(postBalance.toString()) + message: "Escrowed WFLOW balance did not increment after wrapping FLOW - pre: \(preBalance.toString()) | post: \(postBalance.toString())" ) // Confirm bridge COA's WFLOW balance has incremented by the expected amount assert( postBalance - preBalance == uintAmount, - message: "Escrowed WFLOW balance after wrapping does not match requested amount - expected: " - .concat((preBalance + uintAmount).toString()) - .concat(" | actual: ") - .concat((postBalance - preBalance).toString()) + message: "Escrowed WFLOW balance after wrapping does not match requested amount - expected: \(preBalance + uintAmount).toString()) | actual: \(postBalance - preBalance).toString())" ) // Transfer WFLOW to recipient @@ -307,7 +304,7 @@ access(all) contract FlowEVMBridgeHandlers { owner: EVM.EVMAddress, type: Type, amount: UInt256, - protectedTransferCall: fun (): EVM.Result + protectedTransferCall: fun (): EVM.ResultDecoded ): @{FungibleToken.Vault} { let wflowAddress = self.getTargetEVMAddress()! @@ -318,8 +315,7 @@ access(all) contract FlowEVMBridgeHandlers { ) assert( ufixAmount > 0.0, - message: "Requested UInt256 amount ".concat(amount.toString()).concat(" converted to 0.0 ") - .concat(" - try bridging a larger amount to avoid UFix64 precision loss during conversion") + message: "Requested UInt256 amount \(amount.toString()) converted to 0.0 - try bridging a larger amount to avoid UFix64 precision loss during conversion" ) // Transfers WFLOW to bridge COA as escrow @@ -335,12 +331,13 @@ access(all) contract FlowEVMBridgeHandlers { let preBalance = coa.balance().attoflow // Unwrap the transferred WFLOW to FLOW, giving the bridge COA the necessary FLOW to withdraw from EVM - let unwrapResult = FlowEVMBridgeUtils.call( + let unwrapResult = FlowEVMBridgeUtils.callWithSigAndArgs( signature: "withdraw(uint256)", targetEVMAddress: wflowAddress, args: [amount], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert(unwrapResult.status == EVM.Status.successful, message: "Failed to unwrap WFLOW as FLOW") @@ -349,34 +346,29 @@ access(all) contract FlowEVMBridgeHandlers { // Cover underflow assert( postBalance > preBalance, - message: "Escrowed FLOW Balance did not increment after unwrapping WFLOW - pre: ".concat(preBalance.toString()) - .concat(" | post: ").concat(postBalance.toString()) + message: "Escrowed FLOW Balance did not increment after unwrapping WFLOW - pre: \(preBalance.toString()) | post: \(postBalance.toString())" ) // Confirm bridge COA's FLOW balance has incremented by the expected amount assert( UInt256(postBalance - preBalance) == amount, - message: "Escrowed WFLOW balance after unwrapping does not match requested amount - expected: " - .concat((UInt256(preBalance) + amount).toString()) - .concat(" | actual: ") - .concat((postBalance - preBalance).toString()) + message: "Escrowed WFLOW balance after unwrapping does not match requested amount - expected: \(UInt256(preBalance) + amount).toString()) | actual: \(postBalance - preBalance).toString())" ) - // Withdraw escrowed FLOW from bridge COA + // Withdraw escrowed FLOW from bridge COA. + // EVM.Balance takes a UInt (64-bit on all supported platforms). `UInt(amount)` truncates silently if + // `amount > UInt.max`. The assert immediately below catches any truncation: if truncation occurred, + // `UInt256(withdrawBalance.attoflow) != amount` and the transaction reverts. In practice this cannot + // trigger: total FLOW supply is ~1.25B × 10^18 attoflow ≈ 1.25e27, far below UInt.max (~1.8e19 × 1e9 + // = 1.8e28 for 64-bit). No valid WFLOW bridge request can produce an amount large enough to truncate. let withdrawBalance = EVM.Balance(attoflow: UInt(amount)) assert( UInt256(withdrawBalance.attoflow) == amount, - message: "Requested balance failed to convert to attoflow - expected: " - .concat(amount.toString()) - .concat(" | actual: ") - .concat(withdrawBalance.attoflow.toString()) + message: "Requested balance failed to convert to attoflow - expected: \(amount.toString()) | actual: \(withdrawBalance.attoflow.toString())" ) let flowVault <- coa.withdraw(balance: withdrawBalance) assert( flowVault.balance == ufixAmount, - message: "Resulting FLOW Vault balance does not match requested amount - expected: " - .concat(ufixAmount.toString()) - .concat(" | actual: ") - .concat(flowVault.balance.toString()) + message: "Resulting FLOW Vault balance does not match requested amount - expected: \(ufixAmount.toString()) | actual: \(flowVault.balance.toString())" ) return <-flowVault } @@ -388,15 +380,13 @@ access(all) contract FlowEVMBridgeHandlers { /// Sets the target type for the handler access(FlowEVMBridgeHandlerInterfaces.Admin) fun setTargetType(_ type: Type) { - panic("WFLOWTokenHandler has targetType set to " - .concat(self.targetType.identifier).concat(" at initialization")) + panic("WFLOWTokenHandler has targetType set to \(self.targetType.identifier) at initialization") } /// Sets the target EVM address for the handler access(FlowEVMBridgeHandlerInterfaces.Admin) fun setTargetEVMAddress(_ address: EVM.EVMAddress) { - panic("WFLOWTokenHandler has EVMAddress set to " - .concat(self.targetEVMAddress.toString()).concat(" at initialization")) + panic("WFLOWTokenHandler has EVMAddress set to \(self.targetEVMAddress.toString()) at initialization") } /// Sets the target type for the handler diff --git a/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc b/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc index 361f3163..ca32e8ec 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc @@ -1,14 +1,14 @@ -import FungibleToken from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 -import MetadataViews from 0x1d7e57aa55817448 -import ViewResolver from 0x1d7e57aa55817448 -import FlowToken from 0x1654653399040a61 +import "FungibleToken" +import "NonFungibleToken" +import "MetadataViews" +import "ViewResolver" +import "FlowToken" -import EVM from 0xe467b9dd11fa00df +import "EVM" -import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 -import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 -import CrossVMNFT from 0x1e4aa0b87d10b141 +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "CrossVMNFT" /// This escrow contract handles the locking of assets that are bridged from Flow to EVM and retrieval of locked /// assets in escrow when they are bridged back to Flow. diff --git a/cadence/contracts/bridge/FlowEVMBridgeResolver.cdc b/cadence/contracts/bridge/FlowEVMBridgeResolver.cdc index bbbd6c09..fc1ee851 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeResolver.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeResolver.cdc @@ -1,12 +1,12 @@ -import MetadataViews from 0x1d7e57aa55817448 -import FungibleTokenMetadataViews from 0xf233dcee88fe0abe -import FungibleToken from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 - -import ICrossVMAsset from 0x1e4aa0b87d10b141 -import CrossVMToken from 0x1e4aa0b87d10b141 -import CrossVMNFT from 0x1e4aa0b87d10b141 -import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 +import "MetadataViews" +import "FungibleTokenMetadataViews" +import "FungibleToken" +import "NonFungibleToken" + +import "ICrossVMAsset" +import "CrossVMToken" +import "CrossVMNFT" +import "FlowEVMBridgeUtils" /// This contract serves as a metadata resolver for shared views on NFTs and FungibleTokens bridged from EVM to Cadence. /// Upon bridging to Cadence, these tokens must have certain basic metadata views that cannot be retrieved at the time diff --git a/cadence/contracts/bridge/FlowEVMBridgeTemplates.cdc b/cadence/contracts/bridge/FlowEVMBridgeTemplates.cdc index a78d57dd..0ec78fcc 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeTemplates.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeTemplates.cdc @@ -1,9 +1,9 @@ -import FungibleToken from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 +import "FungibleToken" +import "NonFungibleToken" -import EVM from 0xe467b9dd11fa00df +import "EVM" -import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 +import "FlowEVMBridgeUtils" /// This contract serves Cadence code from chunked templates, replacing the contract name with the name derived from /// given arguments - either Cadence Type or EVM contract address. diff --git a/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc b/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc index 7e0a5e2b..6b91651b 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc @@ -1,16 +1,16 @@ -import Burner from 0xf233dcee88fe0abe -import FungibleToken from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 -import MetadataViews from 0x1d7e57aa55817448 -import ViewResolver from 0x1d7e57aa55817448 -import FlowToken from 0x1654653399040a61 - -import EVM from 0xe467b9dd11fa00df - -import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 -import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 -import CrossVMToken from 0x1e4aa0b87d10b141 +import "Burner" +import "FungibleToken" +import "NonFungibleToken" +import "MetadataViews" +import "ViewResolver" +import "FlowToken" + +import "EVM" + +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "CrossVMToken" /// This escrow contract handles the locking of fungible tokens that are bridged from Cadence to EVM and retrieval of /// locked assets in escrow when they are bridged back to Cadence. diff --git a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc index 571b860b..b5cf981a 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc @@ -1,21 +1,68 @@ -import NonFungibleToken from 0x1d7e57aa55817448 -import FungibleToken from 0xf233dcee88fe0abe -import MetadataViews from 0x1d7e57aa55817448 -import CrossVMMetadataViews from 0x1d7e57aa55817448 -import FungibleTokenMetadataViews from 0xf233dcee88fe0abe -import ViewResolver from 0x1d7e57aa55817448 -import FlowToken from 0x1654653399040a61 -import FlowStorageFees from 0xe467b9dd11fa00df - -import EVM from 0xe467b9dd11fa00df - -import SerializeMetadata from 0x1e4aa0b87d10b141 -import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 -import CrossVMNFT from 0x1e4aa0b87d10b141 -import IBridgePermissions from 0x1e4aa0b87d10b141 +import "NonFungibleToken" +import "FungibleToken" +import "MetadataViews" +import "CrossVMMetadataViews" +import "FungibleTokenMetadataViews" +import "ViewResolver" +import "FlowToken" +import "FlowStorageFees" + +import "EVM" + +import "SerializeMetadata" +import "FlowEVMBridgeConfig" +import "CrossVMNFT" +import "IBridgePermissions" /// This contract serves as a source of utility methods leveraged by FlowEVMBridge contracts -// + +/*************************************************************************************** + Clarification of Flow EVM Bridge Security Model + + The bridge operates under a "trust-but-verify" model for EVM contracts: + + Trust Assumptions + - ERC20/ERC721 contracts correctly implement their respective standards. + - `balanceOf()`, `ownerOf()`, and similar view functions return accurate state. + - `transfer()`, `transferFrom()`, and similar mutating functions perform real state changes. + - Emitted events correspond to actual state transitions. + + Security Boundaries + The bridge provides the following security guarantees: + + 1. Type Isolation: Each bridged ERC20/ERC721 maps to a unique Cadence type + (e.g., `EVMVMBridgedToken_0x{ADDRESS}.Vault`). Cadence's type system ensures + complete isolation between different token types. + + 2. Cross-Asset Protection: A vulnerability or malicious behavior in one bridged + asset CANNOT affect any other bridged asset. Each type has independent accounting. + + 3. Protocol Asset Protection: Core protocol assets (FlowToken, etc.) are not + exposed to risks from user-deployed EVM contracts. + + Flow EVM Bridge design - Security Consideration + + The bridge CANNOT protect against: + + - Malicious ERC20/ERC721 contracts that return fabricated data from view functions + or fail to perform real state changes. Such contracts can cause accounting desync + for their own token type, but cannot affect other assets. + + - Token developer malfeasance ("rug pulls"). A developer who controls a token + contract can harm holders of that specific token through various means, with or + without the bridge. + + + The bridge's role is to faithfully execute cross-VM transfers for well-behaved + contracts, not to enforce correct behavior of arbitrary EVM code. + + User Responsibility + + Users interacting with bridged assets should: + - Verify the legitimacy of ERC20/ERC721 contracts before bridging. + - Understand that the bridge cannot guarantee the behavior of arbitrary EVM contracts. +***************************************************************************************/ + access(all) contract FlowEVMBridgeUtils { @@ -125,9 +172,9 @@ contract FlowEVMBridgeUtils { access(all) view fun typeAllowsBridging(_ type: Type): Bool { let contractAddress = self.getContractAddress(fromType: type) - ?? panic("Could not construct contract address from type identifier: ".concat(type.identifier)) + ?? panic("Could not construct contract address from type identifier: \(type.identifier)") let contractName = self.getContractName(fromType: type) - ?? panic("Could not construct contract name from type identifier: ".concat(type.identifier)) + ?? panic("Could not construct contract name from type identifier: \(type.identifier)") if let bridgePermissions = getAccount(contractAddress).contracts.borrow<&{IBridgePermissions}>(name: contractName) { return bridgePermissions.allowsBridging() } @@ -142,20 +189,20 @@ contract FlowEVMBridgeUtils { /// access(all) fun evmAddressAllowsBridging(_ address: EVM.EVMAddress): Bool { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "allowsBridging()", targetEVMAddress: address, args: [], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) // Contract doesn't support the method - proceed permissionlessly if callResult.status != EVM.Status.successful { return true } // Contract is IBridgePermissions - return the result - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) as! [AnyStruct] - return (decodedResult.length == 1 && decodedResult[0] as! Bool) == true ? true : false + return (callResult.results.length == 1 && callResult.results[0] as! Bool) == true ? true : false } /// Identifies if an asset is Cadence- or EVM-native, defined by whether a bridge contract defines it or not @@ -167,7 +214,7 @@ contract FlowEVMBridgeUtils { access(all) view fun isCadenceNative(type: Type): Bool { let definingAddress = self.getContractAddress(fromType: type) - ?? panic("Could not construct address from type identifier: ".concat(type.identifier)) + ?? panic("Could not construct address from type identifier: \(type.identifier)") return definingAddress != self.account.address } @@ -183,7 +230,7 @@ contract FlowEVMBridgeUtils { access(all) view fun isBridgeDefined(type: Type): Bool { let definingAddress = self.getContractAddress(fromType: type) - ?? panic("Could not construct address from type identifier: ".concat(type.identifier)) + ?? panic("Could not construct address from type identifier: \(type.identifier)") return definingAddress == self.account.address } @@ -209,19 +256,19 @@ contract FlowEVMBridgeUtils { access(all) fun isEVMContractBridgeOwned(evmContractAddress: EVM.EVMAddress): Bool { // Ask the bridge factory if the given contract address was deployed by the bridge - let callResult = self.dryCall( - signature: "isBridgeDeployed(address)", - targetEVMAddress: self.bridgeFactoryEVMAddress, - args: [evmContractAddress], - gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 - ) + let callResult = self.dryCallWithSigAndArgs( + signature: "isBridgeDeployed(address)", + targetEVMAddress: self.bridgeFactoryEVMAddress, + args: [evmContractAddress], + gasLimit: FlowEVMBridgeConfig.gasLimit, + value: 0.0, + resultTypes: [Type()] + ) assert(callResult.status == EVM.Status.successful, message: "Call to bridge factory failed") - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) - assert(decodedResult.length == 1, message: "Invalid response length") + assert(callResult.results.length == 1, message: "Invalid response length") - return decodedResult[0] as! Bool + return callResult.results[0] as! Bool } /// Identifies if an asset is ERC721. Reverts on EVM call failure. @@ -232,19 +279,19 @@ contract FlowEVMBridgeUtils { /// access(all) fun isERC721(evmContractAddress: EVM.EVMAddress): Bool { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "isERC721(address)", targetEVMAddress: self.bridgeFactoryEVMAddress, args: [evmContractAddress], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) assert(callResult.status == EVM.Status.successful, message: "Call to bridge factory failed") - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) - assert(decodedResult.length == 1, message: "Invalid response length") + assert(callResult.results.length == 1, message: "Invalid response length") - return decodedResult[0] as! Bool + return callResult.results[0] as! Bool } /// Identifies if an asset is ERC20 as far as is possible without true EVM type introspection. Reverts on EVM call @@ -256,19 +303,19 @@ contract FlowEVMBridgeUtils { /// access(all) fun isERC20(evmContractAddress: EVM.EVMAddress): Bool { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "isERC20(address)", targetEVMAddress: self.bridgeFactoryEVMAddress, args: [evmContractAddress], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) assert(callResult.status == EVM.Status.successful, message: "Call to bridge factory failed") - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) - assert(decodedResult.length == 1, message: "Invalid response length") + assert(callResult.results.length == 1, message: "Invalid response length") - return decodedResult[0] as! Bool + return callResult.results[0] as! Bool } /// Returns whether the contract address is either an ERC721 or ERC20 exclusively. Reverts on EVM call failure. @@ -279,16 +326,16 @@ contract FlowEVMBridgeUtils { /// access(all) fun isValidEVMAsset(evmContractAddress: EVM.EVMAddress): Bool { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "isValidAsset(address)", targetEVMAddress: self.bridgeFactoryEVMAddress, args: [evmContractAddress], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) - assert(decodedResult.length == 1, message: "Invalid response length") - return decodedResult[0] as! Bool + assert(callResult.results.length == 1, message: "Invalid response length") + return callResult.results[0] as! Bool } /// Returns whether the given type is either an NFT or FT exclusively @@ -334,10 +381,10 @@ contract FlowEVMBridgeUtils { // Retrieve the Cadence type's defining contract name, address, & its identifier var name = self.getContractName(fromType: forAssetType) - ?? panic("Could not contract name from type: ".concat(forAssetType.identifier)) + ?? panic("Could not contract name from type: \(forAssetType.identifier)") let identifier = forAssetType.identifier let cadenceAddress = self.getContractAddress(fromType: forAssetType) - ?? panic("Could not derive contract address for token type: ".concat(identifier)) + ?? panic("Could not derive contract address for token type: \(identifier)") // Initialize asset symbol which will be assigned later // based on presence of asset-defined metadata var symbol: String? = nil @@ -364,7 +411,7 @@ contract FlowEVMBridgeUtils { ) as! MetadataViews.NFTCollectionDisplay? { name = collectionDisplay.name let serializedDisplay = SerializeMetadata.serializeFromDisplays(nftDisplay: nil, collectionDisplay: collectionDisplay)! - contractURI = "data:application/json;utf8,{".concat(serializedDisplay).concat("}") + contractURI = "data:application/json;utf8,{\(serializedDisplay)}" } if symbol == nil { symbol = SerializeMetadata.deriveSymbol(fromString: name) @@ -380,7 +427,11 @@ contract FlowEVMBridgeUtils { } if contractURI.length == 0 && ftDisplay != nil { let serializedDisplay = SerializeMetadata.serializeFTDisplay(ftDisplay!) - contractURI = "data:application/json;utf8,{".concat(serializedDisplay).concat("}") + contractURI = "data:application/json;utf8,{\(serializedDisplay)}" + } + // Derive a symbol from the name if no metadata view provided one + if symbol == nil { + symbol = SerializeMetadata.deriveSymbol(fromString: name) } } } @@ -424,7 +475,7 @@ contract FlowEVMBridgeUtils { let isERC20 = self.isERC20(evmContractAddress: evmContractAddress) assert( isERC20, - message: "Contract ".concat(evmContractAddress.toString()).concat("defines an asset that is not currently supported by the bridge") + message: "Contract \(evmContractAddress.toString())defines an asset that is not currently supported by the bridge" ) cadenceContractName = self.deriveBridgedTokenContractName(from: evmContractAddress) decimals = self.getTokenDecimals(evmContractAddress: evmContractAddress) @@ -475,19 +526,19 @@ contract FlowEVMBridgeUtils { /// access(all) fun getName(evmContractAddress: EVM.EVMAddress): String { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "name()", targetEVMAddress: evmContractAddress, args: [], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) assert(callResult.status == EVM.Status.successful, message: "Call for EVM asset name failed") - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) as! [AnyStruct] - assert(decodedResult.length == 1, message: "Invalid response length") + assert(callResult.results.length == 1, message: "Invalid response length") - return decodedResult[0] as! String + return callResult.results[0] as! String } /// Retrieves the NFT/FT symbol from the given EVM contract address - applies for both ERC20 & ERC721 @@ -499,17 +550,17 @@ contract FlowEVMBridgeUtils { /// access(all) fun getSymbol(evmContractAddress: EVM.EVMAddress): String { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "symbol()", targetEVMAddress: evmContractAddress, args: [], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) assert(callResult.status == EVM.Status.successful, message: "Call for EVM asset symbol failed") - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) as! [AnyStruct] - assert(decodedResult.length == 1, message: "Invalid response length") - return decodedResult[0] as! String + assert(callResult.results.length == 1, message: "Invalid response length") + return callResult.results[0] as! String } /// Retrieves the tokenURI for the given NFT ID from the given EVM contract address. Reverts on EVM call failure. @@ -522,19 +573,19 @@ contract FlowEVMBridgeUtils { /// access(all) fun getTokenURI(evmContractAddress: EVM.EVMAddress, id: UInt256): String { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "tokenURI(uint256)", targetEVMAddress: evmContractAddress, args: [id], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) assert(callResult.status == EVM.Status.successful, message: "Call to EVM for tokenURI failed") - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) as! [AnyStruct] - assert(decodedResult.length == 1, message: "Invalid response length") + assert(callResult.results.length == 1, message: "Invalid response length") - return decodedResult[0] as! String + return callResult.results[0] as! String } /// Retrieves the contract URI from the given EVM contract address. Returns nil on EVM call failure. @@ -545,18 +596,18 @@ contract FlowEVMBridgeUtils { /// access(all) fun getContractURI(evmContractAddress: EVM.EVMAddress): String? { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "contractURI()", targetEVMAddress: evmContractAddress, args: [], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) if callResult.status != EVM.Status.successful { return nil } - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) as! [AnyStruct] - return decodedResult.length == 1 ? decodedResult[0] as! String : nil + return callResult.results.length == 1 ? callResult.results[0] as! String : nil } /// Retrieves the number of decimals for a given ERC20 contract address. Reverts on EVM call failure. @@ -567,19 +618,19 @@ contract FlowEVMBridgeUtils { /// access(all) fun getTokenDecimals(evmContractAddress: EVM.EVMAddress): UInt8 { - let callResult = self.dryCall( - signature: "decimals()", - targetEVMAddress: evmContractAddress, - args: [], - gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 - ) + let callResult = self.dryCallWithSigAndArgs( + signature: "decimals()", + targetEVMAddress: evmContractAddress, + args: [], + gasLimit: FlowEVMBridgeConfig.gasLimit, + value: 0.0, + resultTypes: [Type()] + ) assert(callResult.status == EVM.Status.successful, message: "Call for EVM asset decimals failed") - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) as! [AnyStruct] - assert(decodedResult.length == 1, message: "Invalid response length") + assert(callResult.results.length == 1, message: "Invalid response length") - return decodedResult[0] as! UInt8 + return callResult.results[0] as! UInt8 } /// Determines if the provided owner address is either the owner or approved for the NFT in the ERC721 contract @@ -619,18 +670,18 @@ contract FlowEVMBridgeUtils { /// access(all) fun ownerOf(id: UInt256, evmContractAddress: EVM.EVMAddress): EVM.EVMAddress? { - let callResult = self.dryCall( - signature: "ownerOf(uint256)", - targetEVMAddress: evmContractAddress, - args: [id], - gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 - ) + let callResult = self.dryCallWithSigAndArgs( + signature: "ownerOf(uint256)", + targetEVMAddress: evmContractAddress, + args: [id], + gasLimit: FlowEVMBridgeConfig.gasLimit, + value: 0.0, + resultTypes: [Type()] + ) if callResult.status == EVM.Status.failed { return nil } - let decodedCallResult = EVM.decodeABI(types: [Type()], data: callResult.data) - return decodedCallResult.length == 1 ? decodedCallResult[0] as! EVM.EVMAddress : nil + return callResult.results.length == 1 ? callResult.results[0] as! EVM.EVMAddress : nil } /// Returns whether the given owner is approved for the given NFT. Reverts on EVM call failure. @@ -643,17 +694,17 @@ contract FlowEVMBridgeUtils { /// access(all) fun isApproved(ofNFT: UInt256, owner: EVM.EVMAddress, evmContractAddress: EVM.EVMAddress): Bool { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "getApproved(uint256)", targetEVMAddress: evmContractAddress, args: [ofNFT], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) assert(callResult.status == EVM.Status.successful, message: "Call to ERC721.getApproved(uint256) failed") - let decodedCallResult = EVM.decodeABI(types: [Type()], data: callResult.data) - if decodedCallResult.length == 1 { - let actualApproved = decodedCallResult[0] as! EVM.EVMAddress + if callResult.results.length == 1 { + let actualApproved = callResult.results[0] as! EVM.EVMAddress return actualApproved.equals(owner) } return false @@ -670,18 +721,16 @@ contract FlowEVMBridgeUtils { /// access(all) fun erc721Exists(erc721Address: EVM.EVMAddress, id: UInt256): Bool { - let existsResponse = EVM.decodeABI( - types: [Type()], - data: self.dryCall( - signature: "exists(uint256)", - targetEVMAddress: erc721Address, - args: [id], - gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 - ).data, - ) - assert(existsResponse.length == 1, message: "Invalid response length") - return existsResponse[0] as! Bool + let callResult = self.dryCallWithSigAndArgs( + signature: "exists(uint256)", + targetEVMAddress: erc721Address, + args: [id], + gasLimit: FlowEVMBridgeConfig.gasLimit, + value: 0.0, + resultTypes: [Type()] + ) + assert(callResult.results.length == 1, message: "Invalid response length") + return callResult.results[0] as! Bool } /// Returns the ERC20 balance of the owner at the given ERC20 contract address. Reverts on EVM call failure. @@ -694,17 +743,17 @@ contract FlowEVMBridgeUtils { /// access(all) fun balanceOf(owner: EVM.EVMAddress, evmContractAddress: EVM.EVMAddress): UInt256 { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "balanceOf(address)", targetEVMAddress: evmContractAddress, args: [owner], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) assert(callResult.status == EVM.Status.successful, message: "Call to ERC20.balanceOf(address) failed") - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) as! [AnyStruct] - assert(decodedResult.length == 1, message: "Invalid response length") - return decodedResult[0] as! UInt256 + assert(callResult.results.length == 1, message: "Invalid response length") + return callResult.results[0] as! UInt256 } /// Determines if the owner has sufficient funds to bridge the given amount at the ERC20 contract address @@ -729,17 +778,17 @@ contract FlowEVMBridgeUtils { /// access(all) fun totalSupply(evmContractAddress: EVM.EVMAddress): UInt256 { - let callResult = self.dryCall( + let callResult = self.dryCallWithSigAndArgs( signature: "totalSupply()", targetEVMAddress: evmContractAddress, args: [], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) assert(callResult.status == EVM.Status.successful, message: "Call to ERC20.totalSupply() failed") - let decodedResult = EVM.decodeABI(types: [Type()], data: callResult.data) as! [AnyStruct] - assert(decodedResult.length == 1, message: "Invalid response length") - return decodedResult[0] as! UInt256 + assert(callResult.results.length == 1, message: "Invalid response length") + return callResult.results[0] as! UInt256 } /// Converts the given amount of ERC20 tokens to the equivalent amount in FLOW tokens based on the ERC20s decimals @@ -773,6 +822,23 @@ contract FlowEVMBridgeUtils { return self.ufix64ToUInt256(value: amount, decimals: self.getTokenDecimals(evmContractAddress: erc20Address)) } + /// Converts the given ERC20 amount to the corresponding ERC20 amount after rounding down to the maximum precision + /// representable by UFix64 (8 decimal places). This ensures that the amount escrowed on the EVM side matches + /// exactly the amount that will be minted or unlocked on the Cadence side, preventing sub-UFix64-precision + /// "dust" from being permanently locked in escrow without a corresponding Cadence representation. + /// + /// @param amount: The UInt256 ERC20 amount to cast to Cadence precision + /// @param erc20Address: The EVM contract address of the ERC20 token + /// + /// @return The ERC20 UInt256 amount corresponding to the truncated Cadence UFix64 amount + /// + access(all) + fun castERC20AmountToCadencePrecision(_ amount: UInt256, erc20Address: EVM.EVMAddress): UInt256 { + let decimals = self.getTokenDecimals(evmContractAddress: erc20Address) + let ufixAmount = self.uint256ToUFix64(value: amount, decimals: decimals) + return self.ufix64ToUInt256(value: ufixAmount, decimals: decimals) + } + /// Gets the declared Cadence contract address declared by an EVM contract in conformance to the ICrossVM.sol /// contract interface. Reverts if the EVM call is unsuccessful. /// NOTE: Just because an EVM contract declares an association does not mean it it is valid! @@ -784,21 +850,21 @@ contract FlowEVMBridgeUtils { /// access(all) fun getDeclaredCadenceAddressFromCrossVM(evmContract: EVM.EVMAddress): Address? { - let cadenceAddrRes = self.dryCall( + let cadenceAddrRes = self.dryCallWithSigAndArgs( signature: "getCadenceAddress()", targetEVMAddress: evmContract, args: [], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) if cadenceAddrRes.status != EVM.Status.successful { return nil } - let decodedCadenceAddr = EVM.decodeABI(types: [Type()], data: cadenceAddrRes.data) - assert(decodedCadenceAddr.length == 1) - var cadenceAddrStr = decodedCadenceAddr[0] as! String + assert(cadenceAddrRes.results.length == 1, message: "Invalid response length") + var cadenceAddrStr = cadenceAddrRes.results[0] as! String if cadenceAddrStr[1] != "x" { - cadenceAddrStr = "0x".concat(cadenceAddrStr) + cadenceAddrStr = "0x\(cadenceAddrStr)" } return Address.fromString(cadenceAddrStr) ?? nil } @@ -815,19 +881,19 @@ contract FlowEVMBridgeUtils { /// access(all) fun getDeclaredCadenceTypeFromCrossVM(evmContract: EVM.EVMAddress): Type? { - let cadenceIdentifierRes = self.dryCall( + let cadenceIdentifierRes = self.dryCallWithSigAndArgs( signature: "getCadenceIdentifier()", targetEVMAddress: evmContract, args: [], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) if cadenceIdentifierRes.status != EVM.Status.successful { return nil } - let decodedCadenceIdentifier = EVM.decodeABI(types: [Type()], data: cadenceIdentifierRes.data) - assert(decodedCadenceIdentifier.length == 1) - let cadenceIdentifier = decodedCadenceIdentifier[0] as! String + assert(cadenceIdentifierRes.results.length == 1, message: "Invalid response length") + let cadenceIdentifier = cadenceIdentifierRes.results[0] as! String return CompositeType(cadenceIdentifier) ?? nil } @@ -842,21 +908,21 @@ contract FlowEVMBridgeUtils { access(all) fun supportsICrossVMBridgeERC721Fulfillment(evmContract: EVM.EVMAddress): Bool { let interfaceID = EVM.EVMBytes4(value: "2e608d70".decodeHex().toConstantSized<[UInt8; 4]>()!) - let supportsRes = self.dryCall( + let supportsRes = self.dryCallWithSigAndArgs( signature: "supportsInterface(bytes4)", targetEVMAddress: evmContract, args: [interfaceID], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) if supportsRes.status != EVM.Status.successful { return false } - let decodedSupports = EVM.decodeABI(types: [Type()], data: supportsRes.data) - if decodedSupports.length != 1 { + if supportsRes.results.length != 1 { return false } - return decodedSupports[0] as! Bool + return supportsRes.results[0] as! Bool } /// Returns whether the provided EVM contract conforms to ICrossVMBridgeCallable.sol contract interface. @@ -870,21 +936,21 @@ contract FlowEVMBridgeUtils { access(all) fun supportsICrossVMBridgeCallable(evmContract: EVM.EVMAddress): Bool { let interfaceID = EVM.EVMBytes4(value: "b7f9a9ec".decodeHex().toConstantSized<[UInt8; 4]>()!) - let supportsRes = self.dryCall( + let supportsRes = self.dryCallWithSigAndArgs( signature: "supportsInterface(bytes4)", targetEVMAddress: evmContract, args: [interfaceID], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) if supportsRes.status != EVM.Status.successful { return false } - let decodedSupports = EVM.decodeABI(types: [Type()], data: supportsRes.data) - if decodedSupports.length != 1 { + if supportsRes.results.length != 1 { return false } - return decodedSupports[0] as! Bool + return supportsRes.results[0] as! Bool } /// Returns whether the provided EVM contract conforms to both ICrossVMBridgeERC721Fulfillment and @@ -909,18 +975,18 @@ contract FlowEVMBridgeUtils { /// access(all) fun getVMBridgeAddressFromICrossVMBridgeCallable(evmContract: EVM.EVMAddress): EVM.EVMAddress? { - let cadenceIdentifierRes = self.dryCall( + let cadenceIdentifierRes = self.dryCallWithSigAndArgs( signature: "vmBridgeAddress()", targetEVMAddress: evmContract, args: [], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) if cadenceIdentifierRes.status != EVM.Status.successful { return nil } - let decodedCadenceIdentifier = EVM.decodeABI(types: [Type()], data: cadenceIdentifierRes.data) - return decodedCadenceIdentifier.length == 1 ? decodedCadenceIdentifier[0] as! EVM.EVMAddress : nil + return cadenceIdentifierRes.results.length == 1 ? cadenceIdentifierRes.results[0] as! EVM.EVMAddress : nil } /************************ @@ -947,14 +1013,11 @@ contract FlowEVMBridgeUtils { } assert(prefix.length > 1, message: "Invalid prefix") if let splitIdentifier = self.splitObjectIdentifier(identifier: fromType.identifier) { - let sourceContractAddress = Address.fromString("0x".concat(splitIdentifier[1]))! + let sourceContractAddress = Address.fromString("0x\(splitIdentifier[1])")! let sourceContractName = splitIdentifier[2] let resourceName = splitIdentifier[3] return StoragePath( - identifier: prefix.concat(self.delimiter) - .concat(sourceContractAddress.toString()).concat(self.delimiter) - .concat(sourceContractName).concat(self.delimiter) - .concat(resourceName) + identifier: "\(prefix)\(self.delimiter)\(sourceContractAddress.toString())\(self.delimiter)\(sourceContractName)\(self.delimiter)\(resourceName)" ) ?? nil } return nil @@ -969,9 +1032,7 @@ contract FlowEVMBridgeUtils { /// access(all) view fun deriveBridgedNFTContractName(from evmContract: EVM.EVMAddress): String { - return self.contractNamePrefixes[Type<@{NonFungibleToken.NFT}>()]!["bridged"]! - .concat(self.delimiter) - .concat(evmContract.toString()) + return "\(self.contractNamePrefixes[Type<@{NonFungibleToken.NFT}>()]!["bridged"]!)\(self.delimiter)\(evmContract.toString())" } /// Derives the Cadence contract name for a given EVM fungible token of the form @@ -983,9 +1044,7 @@ contract FlowEVMBridgeUtils { /// access(all) view fun deriveBridgedTokenContractName(from evmContract: EVM.EVMAddress): String { - return self.contractNamePrefixes[Type<@{FungibleToken.Vault}>()]!["bridged"]! - .concat(self.delimiter) - .concat(evmContract.toString()) + return "\(self.contractNamePrefixes[Type<@{FungibleToken.Vault}>()]!["bridged"]!)\(self.delimiter)\(evmContract.toString())" } /**************** @@ -1029,6 +1088,13 @@ contract FlowEVMBridgeUtils { } /// Converts a UFix64 to a UInt256 + /// + /// Note on overflow safety: `integer * integerMultiplier` where `integerMultiplier = 10^decimals` could + /// theoretically overflow UInt256 for very large `decimals` values (e.g. decimals > ~58 with a non-zero + /// integer part). In practice this is not a concern: ERC20 tokens with more than 18 decimals are outside + /// the ERC20 standard convention, and UFix64.max (~1.84e19) * 10^18 ≈ 1.84e37, well within UInt256 range. + /// The bridge sources `decimals` from on-chain ERC20 `decimals()` calls; a malicious token reporting + /// extreme decimals would only affect its own bridged representation, not other assets. // access(all) view fun ufix64ToUInt256(value: UFix64, decimals: UInt8): UInt256 { @@ -1065,12 +1131,12 @@ contract FlowEVMBridgeUtils { // Ensure the parts do not exceed the max UFix64 value before conversion assert( scaledValue <= UInt256(UFix64.max), - message: "Scaled integer value ".concat(value.toString()).concat(" exceeds max UFix64 value") + message: "Scaled integer value \(value.toString()) exceeds max UFix64 value" ) /// Check for the max value that can be converted to a UFix64 without overflowing assert( scaledValue == UInt256(UFix64.max) ? scaledFractional < 0.09551616 : true, - message: "Scaled integer value ".concat(value.toString()).concat(" exceeds max UFix64 value") + message: "Scaled integer value \(value.toString()) exceeds max UFix64 value" ) return UFix64(scaledValue) + scaledFractional @@ -1186,7 +1252,7 @@ contract FlowEVMBridgeUtils { view fun buildCompositeType(address: Address, contractName: String, resourceName: String): Type? { let addressStr = address.toString() let subtract0x = addressStr.slice(from: 2, upTo: addressStr.length) - let identifier = "A".concat(".").concat(subtract0x).concat(".").concat(contractName).concat(".").concat(resourceName) + let identifier = "A.\(subtract0x).\(contractName).\(resourceName)" return CompositeType(identifier) } @@ -1238,21 +1304,28 @@ contract FlowEVMBridgeUtils { /// Shared helper simplifying calls using the bridge account's COA /// access(account) - fun call( + fun callWithSigAndArgs( signature: String, targetEVMAddress: EVM.EVMAddress, args: [AnyStruct], gasLimit: UInt64, - value: UFix64 - ): EVM.Result { - let calldata = EVM.encodeABIWithSignature(signature, args) - let valueBalance = EVM.Balance(attoflow: 0) - valueBalance.setFLOW(flow: value) - return self.borrowCOA().call( + value: UFix64, + resultTypes: [Type]? + ): EVM.ResultDecoded { + var valueBalance: UInt = 0 + if value > 0.0 { + let balance = EVM.Balance(attoflow: 0) + balance.setFLOW(flow: value) + valueBalance = balance.inAttoFLOW() + } + + return self.borrowCOA().callWithSigAndArgs( to: targetEVMAddress, - data: calldata, + signature: signature, + args: args, gasLimit: gasLimit, - value: valueBalance + value: valueBalance, + resultTypes: resultTypes ) } @@ -1260,21 +1333,28 @@ contract FlowEVMBridgeUtils { /// call within EVM, serving solely as a mechanism for retrieving data from Flow-EVM environment. /// access(account) - fun dryCall( + fun dryCallWithSigAndArgs( signature: String, targetEVMAddress: EVM.EVMAddress, args: [AnyStruct], gasLimit: UInt64, - value: UFix64 - ): EVM.Result { - let calldata = EVM.encodeABIWithSignature(signature, args) - let valueBalance = EVM.Balance(attoflow: 0) - valueBalance.setFLOW(flow: value) - return self.borrowCOA().dryCall( + value: UFix64, + resultTypes: [Type]? + ): EVM.ResultDecoded { + var valueBalance: UInt = 0 + if value > 0.0 { + let balance = EVM.Balance(attoflow: 0) + balance.setFLOW(flow: value) + valueBalance = balance.inAttoFLOW() + } + + return self.borrowCOA().dryCallWithSigAndArgs( to: targetEVMAddress, - data: calldata, + signature: signature, + args: args, gasLimit: gasLimit, - value: valueBalance + value: valueBalance, + resultTypes: resultTypes ) } @@ -1290,12 +1370,13 @@ contract FlowEVMBridgeUtils { assert(bridgePreStatus, message: "Bridge COA does not own ERC721 requesting to be transferred") assert(!toPreStatus, message: "Recipient already owns ERC721 attempting to be transferred") - let transferResult: EVM.Result = self.call( + let transferResult = self.callWithSigAndArgs( signature: "safeTransferFrom(address,address,uint256)", targetEVMAddress: erc721Address, args: [bridgeCOAAddress, to, id], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert( transferResult.status == EVM.Status.successful, @@ -1315,12 +1396,13 @@ contract FlowEVMBridgeUtils { fun mustSafeMintERC721(erc721Address: EVM.EVMAddress, to: EVM.EVMAddress, id: UInt256, uri: String) { let bridgeCOAAddress = self.getBridgeCOAEVMAddress() - let mintResult: EVM.Result = self.call( + let mintResult = self.callWithSigAndArgs( signature: "safeMint(address,uint256,string)", targetEVMAddress: erc721Address, args: [to, id, uri], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert(mintResult.status == EVM.Status.successful, message: "Mint to bridge recipient failed") @@ -1333,12 +1415,13 @@ contract FlowEVMBridgeUtils { /// access(account) fun mustFulfillNFTToEVM(erc721Address: EVM.EVMAddress, to: EVM.EVMAddress, id: UInt256, maybeBytes: EVM.EVMBytes?) { - let fulfillResult = self.call( + let fulfillResult = self.callWithSigAndArgs( signature: "fulfillToEVM(address,uint256,bytes)", targetEVMAddress: erc721Address, args: [to, id, maybeBytes ?? EVM.EVMBytes(value: [])], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert( fulfillResult.status == EVM.Status.successful, @@ -1357,12 +1440,13 @@ contract FlowEVMBridgeUtils { fun mustUpdateTokenURI(erc721Address: EVM.EVMAddress, id: UInt256, uri: String) { let bridgeCOAAddress = self.getBridgeCOAEVMAddress() - let updateResult: EVM.Result = self.call( + let updateResult = self.callWithSigAndArgs( signature: "updateTokenURI(uint256,string)", targetEVMAddress: erc721Address, args: [id, uri], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert(updateResult.status == EVM.Status.successful, message: "URI update failed") } @@ -1376,7 +1460,7 @@ contract FlowEVMBridgeUtils { owner: EVM.EVMAddress, id: UInt256, erc721Address: EVM.EVMAddress, - protectedTransferCall: fun (EVM.EVMAddress): EVM.Result + protectedTransferCall: fun (EVM.EVMAddress): EVM.ResultDecoded ) { // Ensure the named owner is authorized to act on the NFT let isAuthorized = self.isOwnerOrApproved(ofNFT: id, owner: owner, evmContractAddress: erc721Address) @@ -1408,12 +1492,13 @@ contract FlowEVMBridgeUtils { ) let bridgeCOA = self.getBridgeCOAEVMAddress() - let unwrapResult: EVM.Result = self.call( + let unwrapResult = self.callWithSigAndArgs( signature: "withdrawTo(address,uint256[])", targetEVMAddress: erc721WrapperAddress, args: [bridgeCOA, [id]], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert( unwrapResult.status == EVM.Status.successful, @@ -1432,12 +1517,13 @@ contract FlowEVMBridgeUtils { fun mustMintERC20(to: EVM.EVMAddress, amount: UInt256, erc20Address: EVM.EVMAddress) { let toPreBalance = self.balanceOf(owner: to, evmContractAddress: erc20Address) // Mint tokens to the recipient - let mintResult: EVM.Result = self.call( + let mintResult = self.callWithSigAndArgs( signature: "mint(address,uint256)", targetEVMAddress: erc20Address, args: [to, amount], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert(mintResult.status == EVM.Status.successful, message: "Mint to bridge ERC20 contract failed") // Ensure bridge to recipient was succcessful @@ -1462,12 +1548,13 @@ contract FlowEVMBridgeUtils { ) // Transfer tokens to the recipient - let transferResult: EVM.Result = self.call( + let transferResult = self.callWithSigAndArgs( signature: "transfer(address,uint256)", targetEVMAddress: erc20Address, args: [to, amount], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert(transferResult.status == EVM.Status.successful, message: "transfer call to ERC20 contract failed") @@ -1496,7 +1583,7 @@ contract FlowEVMBridgeUtils { owner: EVM.EVMAddress, amount: UInt256, erc20Address: EVM.EVMAddress, - protectedTransferCall: fun (): EVM.Result + protectedTransferCall: fun (): EVM.ResultDecoded ) { // Ensure the caller is has sufficient balance to bridge the requested amount let hasSufficientBalance = self.hasSufficientBalance( @@ -1524,7 +1611,11 @@ contract FlowEVMBridgeUtils { evmContractAddress: erc20Address ) - // Confirm the transfer of the expected was successful in both sending owner and recipient escrow + // Confirm the expected amount moved from owner to bridge escrow. These post-state assertions are the + // primary security guarantee for ERC20 escrow — they catch any accounting discrepancy regardless of + // cause (reentrant callbacks, fee-on-transfer tokens, etc.). The pre-balance snapshot above is not a + // TOCTOU risk because all EVM calls within a single Cadence transaction execute within the same EVM + // block, so no interleaving can occur between the snapshot and the transfer. assert(ownerPostBalance == ownerPreBalance - amount, message: "Transfer to owner failed") assert(bridgePostBalance == bridgePreBalance + amount, message: "Transfer to bridge escrow failed") } @@ -1534,12 +1625,13 @@ contract FlowEVMBridgeUtils { /// access(account) fun mustBurnERC721(erc721Address: EVM.EVMAddress, id: UInt256) { - let burnResult = FlowEVMBridgeUtils.call( + let burnResult = FlowEVMBridgeUtils.callWithSigAndArgs( signature: "burn(uint256)", targetEVMAddress: erc721Address, args: [id], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ) assert(burnResult.status == EVM.Status.successful, message: "0x\(erc721Address.toString()).burn(\(id)) failed with error code \(burnResult.errorCode) and message: \(burnResult.errorMessage)") @@ -1557,17 +1649,17 @@ contract FlowEVMBridgeUtils { isERC721: Bool ): EVM.EVMAddress { let deployerTag = isERC721 ? "ERC721" : "ERC20" - let deployResult: EVM.Result = self.call( + let deployResult = self.callWithSigAndArgs( signature: "deploy(string,string,string,string,string,string)", targetEVMAddress: self.bridgeFactoryEVMAddress, args: [deployerTag, name, symbol, cadenceAddress.toString(), flowIdentifier, contractURI], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: [Type()] ) assert(deployResult.status == EVM.Status.successful, message: "EVM Token contract deployment failed") - let decodedResult: [AnyStruct] = EVM.decodeABI(types: [Type()], data: deployResult.data) - assert(decodedResult.length == 1, message: "Invalid response length") - return decodedResult[0] as! EVM.EVMAddress + assert(deployResult.results.length == 1, message: "Invalid response length") + return deployResult.results[0] as! EVM.EVMAddress } /// Calls `setSymbol(string)` on the EVM contract as exposed on FlowEVMBridgedERC721 contracts, enabling Cadence @@ -1576,12 +1668,13 @@ contract FlowEVMBridgeUtils { /// access(account) fun tryUpdateSymbol(_ evmContractAddress: EVM.EVMAddress, symbol: String): Bool { - return self.call( + return self.callWithSigAndArgs( signature: "setSymbol(string)", targetEVMAddress: evmContractAddress, args: [symbol], gasLimit: FlowEVMBridgeConfig.gasLimit, - value: 0.0 + value: 0.0, + resultTypes: nil ).status == EVM.Status.successful } diff --git a/cadence/contracts/bridge/interfaces/CrossVMNFT.cdc b/cadence/contracts/bridge/interfaces/CrossVMNFT.cdc index a83e7013..68ab131c 100644 --- a/cadence/contracts/bridge/interfaces/CrossVMNFT.cdc +++ b/cadence/contracts/bridge/interfaces/CrossVMNFT.cdc @@ -1,10 +1,10 @@ -import NonFungibleToken from 0x1d7e57aa55817448 -import FungibleToken from 0xf233dcee88fe0abe -import MetadataViews from 0x1d7e57aa55817448 +import "NonFungibleToken" +import "FungibleToken" +import "MetadataViews" -import EVM from 0xe467b9dd11fa00df +import "EVM" -import ICrossVMAsset from 0x1e4aa0b87d10b141 +import "ICrossVMAsset" /// Contract defining cross-VM NFT-related interfaces /// diff --git a/cadence/contracts/bridge/interfaces/CrossVMToken.cdc b/cadence/contracts/bridge/interfaces/CrossVMToken.cdc index 2405fb16..62b3c9ad 100644 --- a/cadence/contracts/bridge/interfaces/CrossVMToken.cdc +++ b/cadence/contracts/bridge/interfaces/CrossVMToken.cdc @@ -1,6 +1,6 @@ -import FungibleToken from 0xf233dcee88fe0abe +import "FungibleToken" -import EVM from 0xe467b9dd11fa00df +import "EVM" /// Contract defining cross-VM Fungible Token Vault interface /// diff --git a/cadence/contracts/bridge/interfaces/FlowEVMBridgeHandlerInterfaces.cdc b/cadence/contracts/bridge/interfaces/FlowEVMBridgeHandlerInterfaces.cdc index 6c8c9dee..b29ef6b7 100644 --- a/cadence/contracts/bridge/interfaces/FlowEVMBridgeHandlerInterfaces.cdc +++ b/cadence/contracts/bridge/interfaces/FlowEVMBridgeHandlerInterfaces.cdc @@ -1,7 +1,7 @@ -import FungibleToken from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 +import "FungibleToken" +import "NonFungibleToken" -import EVM from 0xe467b9dd11fa00df +import "EVM" /// FlowEVMBridgeHandlerInterfaces /// @@ -125,7 +125,7 @@ access(all) contract FlowEVMBridgeHandlerInterfaces { access(Admin) fun disableBridging() { pre { self.isEnabled(): - "Cannot disable: ".concat(self.getType().identifier).concat(" is already disabled") + "Cannot disable: \(self.getType().identifier) is already disabled" } post { !self.isEnabled(): @@ -173,12 +173,9 @@ access(all) contract FlowEVMBridgeHandlerInterfaces { ) { pre { self.isEnabled(): - "TokenHandler ".concat(self.getType().identifier).concat(" with uuid ") - .concat(self.uuid.toString()).concat(" is not yet enabled") + "TokenHandler \(self.getType().identifier) with uuid \(self.uuid.toString()) is not yet enabled" tokens.getType() == self.getTargetType(): - "TokenHandler ".concat(self.getType().identifier).concat(" with uuid ").concat(self.uuid.toString()) - .concat(" expects ").concat(self.getTargetType()?.identifier ?? "nil") - .concat(" but received ").concat(tokens.getType().identifier) + "TokenHandler \(self.getType().identifier) with uuid \(self.uuid.toString()) expects \(self.getTargetType()?.identifier ?? "nil") but received \(tokens.getType().identifier)" tokens.balance > 0.0: "Attempting to bridge 0.0 tokens - zero amounts are unsupported" } @@ -188,13 +185,12 @@ access(all) contract FlowEVMBridgeHandlerInterfaces { owner: EVM.EVMAddress, type: Type, amount: UInt256, - protectedTransferCall: fun (): EVM.Result + protectedTransferCall: fun (): EVM.ResultDecoded ): @{FungibleToken.Vault} { pre { self.isEnabled(): - "TokenHandler ".concat(self.getType().identifier).concat(" with uuid ") - .concat(self.uuid.toString()).concat(" is not yet enabled") - amount > UInt256(0): "Attempting to bridge 0 tokens from EVM - zero amounts are unsupported" + "TokenHandler \(self.getType().identifier) with uuid \(self.uuid.toString()) is not yet enabled" + amount > 0: "Attempting to bridge 0 tokens from EVM - zero amounts are unsupported" } post { result.getType() == self.getTargetType(): diff --git a/cadence/contracts/bridge/interfaces/ICrossVM.cdc b/cadence/contracts/bridge/interfaces/ICrossVM.cdc index 06e45c57..73219e7d 100644 --- a/cadence/contracts/bridge/interfaces/ICrossVM.cdc +++ b/cadence/contracts/bridge/interfaces/ICrossVM.cdc @@ -1,4 +1,4 @@ -import EVM from 0xe467b9dd11fa00df +import "EVM" /// Contract interface denoting a cross-VM implementation, exposing methods to query EVM-associated addresses /// diff --git a/cadence/contracts/bridge/interfaces/ICrossVMAsset.cdc b/cadence/contracts/bridge/interfaces/ICrossVMAsset.cdc index 5ce81157..fb8fa8a1 100644 --- a/cadence/contracts/bridge/interfaces/ICrossVMAsset.cdc +++ b/cadence/contracts/bridge/interfaces/ICrossVMAsset.cdc @@ -1,6 +1,6 @@ -import EVM from 0xe467b9dd11fa00df +import "EVM" -import ICrossVM from 0x1e4aa0b87d10b141 +import "ICrossVM" /// A simple contract interface for a Cadence contract that represents an asset bridged from Flow EVM such as an ERC20 /// or ERC721 token. diff --git a/cadence/contracts/bridge/interfaces/IEVMBridgeNFTMinter.cdc b/cadence/contracts/bridge/interfaces/IEVMBridgeNFTMinter.cdc index 976e47a9..93908c9a 100644 --- a/cadence/contracts/bridge/interfaces/IEVMBridgeNFTMinter.cdc +++ b/cadence/contracts/bridge/interfaces/IEVMBridgeNFTMinter.cdc @@ -1,4 +1,4 @@ -import NonFungibleToken from 0x1d7e57aa55817448 +import "NonFungibleToken" /// Contract interface enabling FlowEVMBridge to mint NFTs from implementing bridge contracts. /// diff --git a/cadence/contracts/bridge/interfaces/IEVMBridgeTokenMinter.cdc b/cadence/contracts/bridge/interfaces/IEVMBridgeTokenMinter.cdc index 2a44b568..f5ce9120 100644 --- a/cadence/contracts/bridge/interfaces/IEVMBridgeTokenMinter.cdc +++ b/cadence/contracts/bridge/interfaces/IEVMBridgeTokenMinter.cdc @@ -1,4 +1,4 @@ -import FungibleToken from 0xf233dcee88fe0abe +import "FungibleToken" /// Contract interface enabling FlowEVMBridge to mint fungible tokens from implementing bridge contracts. /// diff --git a/cadence/contracts/bridge/interfaces/IFlowEVMNFTBridge.cdc b/cadence/contracts/bridge/interfaces/IFlowEVMNFTBridge.cdc index 0dbcfd04..054afb6a 100644 --- a/cadence/contracts/bridge/interfaces/IFlowEVMNFTBridge.cdc +++ b/cadence/contracts/bridge/interfaces/IFlowEVMNFTBridge.cdc @@ -1,10 +1,10 @@ -import FungibleToken from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 +import "FungibleToken" +import "NonFungibleToken" -import EVM from 0xe467b9dd11fa00df +import "EVM" -import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 -import CrossVMNFT from 0x1e4aa0b87d10b141 +import "FlowEVMBridgeConfig" +import "CrossVMNFT" access(all) contract interface IFlowEVMNFTBridge { @@ -74,8 +74,7 @@ access(all) contract interface IFlowEVMNFTBridge { to: to.toString(), evmContractAddress: self.getAssociatedEVMAddress(with: token.getType())?.toString() ?? panic( - "Could not find EVM Contract address associated with provided NFT identifier=" - .concat(token.getType().identifier) + "Could not find EVM Contract address associated with provided NFT identifier=\(token.getType().identifier)" ), bridgeAddress: self.account.address ) @@ -101,7 +100,7 @@ access(all) contract interface IFlowEVMNFTBridge { type: Type, id: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, - protectedTransferCall: fun (EVM.EVMAddress): EVM.Result + protectedTransferCall: fun (EVM.EVMAddress): EVM.ResultDecoded ): @{NonFungibleToken.NFT} { post { emit BridgedNFTFromEVM( diff --git a/cadence/contracts/bridge/interfaces/IFlowEVMTokenBridge.cdc b/cadence/contracts/bridge/interfaces/IFlowEVMTokenBridge.cdc index 942eb8fe..180d6628 100644 --- a/cadence/contracts/bridge/interfaces/IFlowEVMTokenBridge.cdc +++ b/cadence/contracts/bridge/interfaces/IFlowEVMTokenBridge.cdc @@ -1,7 +1,7 @@ -import FungibleToken from 0xf233dcee88fe0abe -import NonFungibleToken from 0x1d7e57aa55817448 +import "FungibleToken" +import "NonFungibleToken" -import EVM from 0xe467b9dd11fa00df +import "EVM" access(all) contract interface IFlowEVMTokenBridge { @@ -68,8 +68,7 @@ access(all) contract interface IFlowEVMTokenBridge { to: to.toString(), evmContractAddress: self.getAssociatedEVMAddress(with: vault.getType())?.toString() ?? panic( - "Could not find EVM Contract address associated with provided Token identifier=" - .concat(vault.getType().identifier) + "Could not find EVM Contract address associated with provided Token identifier=\(vault.getType().identifier)" ), bridgeAddress: self.account.address ) @@ -95,7 +94,7 @@ access(all) contract interface IFlowEVMTokenBridge { type: Type, amount: UInt256, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}, - protectedTransferCall: fun (): EVM.Result + protectedTransferCall: fun (): EVM.ResultDecoded ): @{FungibleToken.Vault} { post { emit BridgedTokensFromEVM( diff --git a/cadence/contracts/example-assets/ExampleNFT.cdc b/cadence/contracts/example-assets/ExampleNFT.cdc index 1576dbf5..1ae72dc9 100644 --- a/cadence/contracts/example-assets/ExampleNFT.cdc +++ b/cadence/contracts/example-assets/ExampleNFT.cdc @@ -10,9 +10,9 @@ * */ -import NonFungibleToken from 0x1d7e57aa55817448 -import ViewResolver from 0x1d7e57aa55817448 -import MetadataViews from 0x1d7e57aa55817448 +import "NonFungibleToken" +import "ViewResolver" +import "MetadataViews" access(all) contract ExampleNFT: NonFungibleToken { diff --git a/cadence/contracts/example-assets/ExampleToken.cdc b/cadence/contracts/example-assets/ExampleToken.cdc index e3c2d16f..91415bad 100644 --- a/cadence/contracts/example-assets/ExampleToken.cdc +++ b/cadence/contracts/example-assets/ExampleToken.cdc @@ -1,6 +1,6 @@ -import FungibleToken from 0xf233dcee88fe0abe -import MetadataViews from 0x1d7e57aa55817448 -import FungibleTokenMetadataViews from 0xf233dcee88fe0abe +import "FungibleToken" +import "MetadataViews" +import "FungibleTokenMetadataViews" access(all) contract ExampleToken: FungibleToken { diff --git a/cadence/contracts/handled-tokens/USDCFlow.cdc b/cadence/contracts/handled-tokens/USDCFlow.cdc index b0cc3e0c..76baa836 100644 --- a/cadence/contracts/handled-tokens/USDCFlow.cdc +++ b/cadence/contracts/handled-tokens/USDCFlow.cdc @@ -1,18 +1,27 @@ -import FungibleToken from 0xf233dcee88fe0abe -import FungibleTokenMetadataViews from 0xf233dcee88fe0abe -import MetadataViews from 0x1d7e57aa55817448 -import Burner from 0xf233dcee88fe0abe -import ViewResolver from 0x1d7e57aa55817448 -import FlowEVMBridgeHandlerInterfaces from 0x1e4aa0b87d10b141 - -/// The `USDCFlow` smart contract is integrated directly -/// with the Flow VM bridge as the bridged version of Flow EVM USDC -/// which is itself the bridged version of official USDC from Ethereum Mainnet. +import "FungibleToken" +import "FungibleTokenMetadataViews" +import "MetadataViews" +import "Burner" + +import "FlowEVMBridgeHandlerInterfaces" +import "FlowEVMBridgeConfig" + +/// USDCFlow +/// +/// Defines the Cadence version of bridged Flow USDC. +/// Before the Sept 4th, 2024 Crescendo migration, users can send +/// `FiatToken` vaults to the `USDCFlow.wrapFiatToken()` function +/// and receive `USDCFlow` vaults back with the exact same balance. + +/// After the Crescendo migration, the `USDCFlow` smart contract +/// will integrate directly with the Flow VM bridge to become +/// the bridged version of Flow EVM USDC. These tokens will be backed +/// by real USDC via Flow EVM. /// This is not the official Circle USDC, only a bridged version /// that is still backed by official USDC on the other side of the bridge -access(all) contract USDCFlow: FungibleToken, ViewResolver { +access(all) contract USDCFlow: FungibleToken { /// Total supply of USDCFlows in existence access(all) var totalSupply: UFix64 @@ -45,18 +54,20 @@ access(all) contract USDCFlow: FungibleToken, ViewResolver { case Type(): let media = MetadataViews.Media( file: MetadataViews.HTTPFile( - url: "https://uploads-ssl.webflow.com/5f734f4dbd95382f4fdfa0ea/66bfae00953c3d7bd09e7ac4_USDC-and-FLOW.svg" + url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg" ), mediaType: "image/svg+xml" ) let medias = MetadataViews.Medias([media]) return FungibleTokenMetadataViews.FTDisplay( - name: "USDC.e (Flow)", - symbol: "USDC.e", - description: "This fungible token representation of Standard Bridged USDC is bridged from Flow EVM.", - externalURL: MetadataViews.ExternalURL("https://github.com/circlefin/stablecoin-evm/blob/master/doc/bridged_USDC_standard.md"), + name: "USDC (Flow)", + symbol: "USDCf", + description: "This fungible token representation of USDC is bridged from Flow EVM.", + externalURL: MetadataViews.ExternalURL("https://www.circle.com/en/usdc"), logos: medias, - socials: {}, + socials: { + "x": MetadataViews.ExternalURL("https://x.com/circle") + } ) case Type(): return FungibleTokenMetadataViews.FTVaultData( @@ -185,6 +196,22 @@ access(all) contract USDCFlow: FungibleToken, ViewResolver { } } + /// Sends the USDCFlow Minter to the Flow/EVM bridge + /// without giving any account access to the minter + /// before it is safely in the decentralized bridge + access(all) fun sendMinterToBridge(_ bridgeAddress: Address) { + let minter <- create Minter() + // borrow a reference to the bridge's configuration admin resource from public Capability + let bridgeAdmin = getAccount(bridgeAddress).capabilities.borrow<&FlowEVMBridgeConfig.Admin>( + FlowEVMBridgeConfig.adminPublicPath + ) ?? panic("FlowEVMBridgeConfig.Admin could not be referenced from ".concat(bridgeAddress.toString())) + + // sets the USDCFlow as the minter resource for all USDCFlow bridge requests + // prior to transferring the Minter, a TokenHandler will be set for USDCFlow during the bridge's initial + // configuration, setting the stage for this minter to be sent. + bridgeAdmin.setTokenHandlerMinter(targetType: Type<@USDCFlow.Vault>(), minter: <-minter) + } + /// createEmptyVault /// /// @return The new Vault resource with a balance of zero @@ -211,4 +238,4 @@ access(all) contract USDCFlow: FungibleToken, ViewResolver { self.account.capabilities.publish(tokenCap, at: self.ReceiverPublicPath) self.account.capabilities.publish(tokenCap, at: self.VaultPublicPath) } -} +} \ No newline at end of file diff --git a/cadence/contracts/utils/ScopedFTProviders.cdc b/cadence/contracts/utils/ScopedFTProviders.cdc index a6ff495f..77c76ced 100644 --- a/cadence/contracts/utils/ScopedFTProviders.cdc +++ b/cadence/contracts/utils/ScopedFTProviders.cdc @@ -1,5 +1,5 @@ -import FungibleToken from 0xf233dcee88fe0abe -import StringUtils from 0x1e4aa0b87d10b141 +import "FungibleToken" +import "StringUtils" // Copied from https://github.com/green-goo-dao/flow-utils/blob/crescendo/contracts/ScopedFTProviders.cdc with minor changes // Special thanks to the Green Goo Dao contributors for creating this contract diff --git a/cadence/contracts/utils/Serialize.cdc b/cadence/contracts/utils/Serialize.cdc index a70f4b8e..41c9892f 100644 --- a/cadence/contracts/utils/Serialize.cdc +++ b/cadence/contracts/utils/Serialize.cdc @@ -1,6 +1,6 @@ -import ViewResolver from 0x1d7e57aa55817448 -import MetadataViews from 0x1d7e57aa55817448 -import NonFungibleToken from 0x1d7e57aa55817448 +import "ViewResolver" +import "MetadataViews" +import "NonFungibleToken" /// This contract is a utility for serializing primitive types, arrays, and common metadata mapping formats to JSON /// compatible strings. Also included are interfaces enabling custom serialization for structs and resources. diff --git a/cadence/contracts/utils/SerializeMetadata.cdc b/cadence/contracts/utils/SerializeMetadata.cdc index f948b582..1ceca382 100644 --- a/cadence/contracts/utils/SerializeMetadata.cdc +++ b/cadence/contracts/utils/SerializeMetadata.cdc @@ -1,9 +1,9 @@ -import ViewResolver from 0x1d7e57aa55817448 -import MetadataViews from 0x1d7e57aa55817448 -import NonFungibleToken from 0x1d7e57aa55817448 -import FungibleTokenMetadataViews from 0xf233dcee88fe0abe +import "ViewResolver" +import "MetadataViews" +import "NonFungibleToken" +import "FungibleTokenMetadataViews" -import Serialize from 0x1e4aa0b87d10b141 +import "Serialize" /// This contract defines methods for serializing NFT metadata as a JSON compatible string, according to the common /// OpenSea metadata format. NFTs and metadata views can be serialized by reference via contract methods. diff --git a/cadence/contracts/utils/StringUtils.cdc b/cadence/contracts/utils/StringUtils.cdc index ffce9f0f..4af734e6 100644 --- a/cadence/contracts/utils/StringUtils.cdc +++ b/cadence/contracts/utils/StringUtils.cdc @@ -1,4 +1,4 @@ -import ArrayUtils from 0x1e4aa0b87d10b141 +import "ArrayUtils" // Copied from https://github.com/green-goo-dao/flow-utils/blob/crescendo/contracts/StringUtils.cdc // Special thanks to the Green Goo Dao contributors for creating this contract From 07929bcc6be83b86aae9d40057aa7638ae2bdfd5 Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:53:45 -0400 Subject: [PATCH 2/2] chore: normalize imports to string format in template files --- .../emulator/EVMBridgedNFTTemplate.cdc | 34 +++++++++---------- .../emulator/EVMBridgedTokenTemplate.cdc | 32 ++++++++--------- .../mainnet/EVMBridgedNFTTemplate.cdc | 34 +++++++++---------- .../mainnet/EVMBridgedTokenTemplate.cdc | 32 ++++++++--------- .../testing/EVMBridgedNFTTemplate.cdc | 34 +++++++++---------- .../testing/EVMBridgedTokenTemplate.cdc | 32 ++++++++--------- .../testnet/EVMBridgedNFTTemplate.cdc | 34 +++++++++---------- .../testnet/EVMBridgedTokenTemplate.cdc | 32 ++++++++--------- 8 files changed, 132 insertions(+), 132 deletions(-) diff --git a/cadence/contracts/templates/emulator/EVMBridgedNFTTemplate.cdc b/cadence/contracts/templates/emulator/EVMBridgedNFTTemplate.cdc index b3a32258..6b9744e0 100644 --- a/cadence/contracts/templates/emulator/EVMBridgedNFTTemplate.cdc +++ b/cadence/contracts/templates/emulator/EVMBridgedNFTTemplate.cdc @@ -1,20 +1,20 @@ -import NonFungibleToken from 0xf8d6e0586b0a20c7 -import MetadataViews from 0xf8d6e0586b0a20c7 -import ViewResolver from 0xf8d6e0586b0a20c7 -import FungibleToken from 0xee82856bf20e2aa6 -import FlowToken from 0x0ae53cb6e3f42a79 - -import EVM from 0xf8d6e0586b0a20c7 - -import ICrossVM from 0xf8d6e0586b0a20c7 -import ICrossVMAsset from 0xf8d6e0586b0a20c7 -import IEVMBridgeNFTMinter from 0xf8d6e0586b0a20c7 -import FlowEVMBridgeNFTEscrow from 0xf8d6e0586b0a20c7 -import FlowEVMBridgeConfig from 0xf8d6e0586b0a20c7 -import FlowEVMBridgeUtils from 0xf8d6e0586b0a20c7 -import FlowEVMBridge from 0xf8d6e0586b0a20c7 -import CrossVMNFT from 0xf8d6e0586b0a20c7 -import FlowEVMBridgeResolver from 0xf8d6e0586b0a20c7 +import "NonFungibleToken" +import "MetadataViews" +import "ViewResolver" +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "ICrossVM" +import "ICrossVMAsset" +import "IEVMBridgeNFTMinter" +import "FlowEVMBridgeNFTEscrow" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +import "CrossVMNFT" +import "FlowEVMBridgeResolver" /// This contract is a template used by FlowEVMBridge to define EVM-native NFTs bridged from Flow EVM to Flow. /// Upon deployment of this contract, the contract name is derived as a function of the asset type (here an ERC721 aka diff --git a/cadence/contracts/templates/emulator/EVMBridgedTokenTemplate.cdc b/cadence/contracts/templates/emulator/EVMBridgedTokenTemplate.cdc index b8169d40..9527e955 100644 --- a/cadence/contracts/templates/emulator/EVMBridgedTokenTemplate.cdc +++ b/cadence/contracts/templates/emulator/EVMBridgedTokenTemplate.cdc @@ -1,21 +1,21 @@ -import NonFungibleToken from 0xf8d6e0586b0a20c7 -import MetadataViews from 0xf8d6e0586b0a20c7 -import FungibleTokenMetadataViews from 0xee82856bf20e2aa6 -import ViewResolver from 0xf8d6e0586b0a20c7 -import FungibleToken from 0xee82856bf20e2aa6 -import FlowToken from 0x0ae53cb6e3f42a79 +import "NonFungibleToken" +import "MetadataViews" +import "FungibleTokenMetadataViews" +import "ViewResolver" +import "FungibleToken" +import "FlowToken" -import EVM from 0xf8d6e0586b0a20c7 +import "EVM" -import ICrossVM from 0xf8d6e0586b0a20c7 -import ICrossVMAsset from 0xf8d6e0586b0a20c7 -import IEVMBridgeTokenMinter from 0xf8d6e0586b0a20c7 -import FlowEVMBridgeTokenEscrow from 0xf8d6e0586b0a20c7 -import FlowEVMBridgeConfig from 0xf8d6e0586b0a20c7 -import FlowEVMBridgeUtils from 0xf8d6e0586b0a20c7 -import FlowEVMBridge from 0xf8d6e0586b0a20c7 -import CrossVMToken from 0xf8d6e0586b0a20c7 -import FlowEVMBridgeResolver from 0xf8d6e0586b0a20c7 +import "ICrossVM" +import "ICrossVMAsset" +import "IEVMBridgeTokenMinter" +import "FlowEVMBridgeTokenEscrow" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +import "CrossVMToken" +import "FlowEVMBridgeResolver" /// This contract is a template used by FlowEVMBridge to define EVM-native fungible tokens bridged from Flow EVM to /// Cadence. Upon deployment of this contract, the contract name is derived as a function of the asset type (here an diff --git a/cadence/contracts/templates/mainnet/EVMBridgedNFTTemplate.cdc b/cadence/contracts/templates/mainnet/EVMBridgedNFTTemplate.cdc index 754bf9e2..6b9744e0 100644 --- a/cadence/contracts/templates/mainnet/EVMBridgedNFTTemplate.cdc +++ b/cadence/contracts/templates/mainnet/EVMBridgedNFTTemplate.cdc @@ -1,20 +1,20 @@ -import NonFungibleToken from 0x1d7e57aa55817448 -import MetadataViews from 0x1d7e57aa55817448 -import ViewResolver from 0x1d7e57aa55817448 -import FungibleToken from 0xf233dcee88fe0abe -import FlowToken from 0x1654653399040a61 - -import EVM from 0xe467b9dd11fa00df - -import ICrossVM from 0x1e4aa0b87d10b141 -import ICrossVMAsset from 0x1e4aa0b87d10b141 -import IEVMBridgeNFTMinter from 0x1e4aa0b87d10b141 -import FlowEVMBridgeNFTEscrow from 0x1e4aa0b87d10b141 -import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 -import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 -import FlowEVMBridge from 0x1e4aa0b87d10b141 -import CrossVMNFT from 0x1e4aa0b87d10b141 -import FlowEVMBridgeResolver from 0x1e4aa0b87d10b141 +import "NonFungibleToken" +import "MetadataViews" +import "ViewResolver" +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "ICrossVM" +import "ICrossVMAsset" +import "IEVMBridgeNFTMinter" +import "FlowEVMBridgeNFTEscrow" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +import "CrossVMNFT" +import "FlowEVMBridgeResolver" /// This contract is a template used by FlowEVMBridge to define EVM-native NFTs bridged from Flow EVM to Flow. /// Upon deployment of this contract, the contract name is derived as a function of the asset type (here an ERC721 aka diff --git a/cadence/contracts/templates/mainnet/EVMBridgedTokenTemplate.cdc b/cadence/contracts/templates/mainnet/EVMBridgedTokenTemplate.cdc index c1e6bf09..66b3752f 100644 --- a/cadence/contracts/templates/mainnet/EVMBridgedTokenTemplate.cdc +++ b/cadence/contracts/templates/mainnet/EVMBridgedTokenTemplate.cdc @@ -1,21 +1,21 @@ -import NonFungibleToken from 0x1d7e57aa55817448 -import MetadataViews from 0x1d7e57aa55817448 -import ViewResolver from 0x1d7e57aa55817448 -import FungibleToken from 0xf233dcee88fe0abe -import FungibleTokenMetadataViews from 0xf233dcee88fe0abe -import FlowToken from 0x1654653399040a61 +import "NonFungibleToken" +import "MetadataViews" +import "ViewResolver" +import "FungibleToken" +import "FungibleTokenMetadataViews" +import "FlowToken" -import EVM from 0xe467b9dd11fa00df +import "EVM" -import ICrossVM from 0x1e4aa0b87d10b141 -import ICrossVMAsset from 0x1e4aa0b87d10b141 -import IEVMBridgeTokenMinter from 0x1e4aa0b87d10b141 -import FlowEVMBridgeTokenEscrow from 0x1e4aa0b87d10b141 -import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 -import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 -import FlowEVMBridge from 0x1e4aa0b87d10b141 -import CrossVMToken from 0x1e4aa0b87d10b141 -import FlowEVMBridgeResolver from 0x1e4aa0b87d10b141 +import "ICrossVM" +import "ICrossVMAsset" +import "IEVMBridgeTokenMinter" +import "FlowEVMBridgeTokenEscrow" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +import "CrossVMToken" +import "FlowEVMBridgeResolver" /// This contract is a template used by FlowEVMBridge to define EVM-native fungible tokens bridged from Flow EVM to /// Cadence. Upon deployment of this contract, the contract name is derived as a function of the asset type (here an diff --git a/cadence/contracts/templates/testing/EVMBridgedNFTTemplate.cdc b/cadence/contracts/templates/testing/EVMBridgedNFTTemplate.cdc index 4634f5df..6b9744e0 100644 --- a/cadence/contracts/templates/testing/EVMBridgedNFTTemplate.cdc +++ b/cadence/contracts/templates/testing/EVMBridgedNFTTemplate.cdc @@ -1,20 +1,20 @@ -import NonFungibleToken from 0x0000000000000001 -import MetadataViews from 0x0000000000000001 -import ViewResolver from 0x0000000000000001 -import FungibleToken from 0x0000000000000002 -import FlowToken from 0x0000000000000003 - -import EVM from 0x0000000000000001 - -import ICrossVM from 0x0000000000000007 -import ICrossVMAsset from 0x0000000000000007 -import IEVMBridgeNFTMinter from 0x0000000000000007 -import FlowEVMBridgeNFTEscrow from 0x0000000000000007 -import FlowEVMBridgeConfig from 0x0000000000000007 -import FlowEVMBridgeUtils from 0x0000000000000007 -import FlowEVMBridge from 0x0000000000000007 -import CrossVMNFT from 0x0000000000000007 -import FlowEVMBridgeResolver from 0x0000000000000007 +import "NonFungibleToken" +import "MetadataViews" +import "ViewResolver" +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "ICrossVM" +import "ICrossVMAsset" +import "IEVMBridgeNFTMinter" +import "FlowEVMBridgeNFTEscrow" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +import "CrossVMNFT" +import "FlowEVMBridgeResolver" /// This contract is a template used by FlowEVMBridge to define EVM-native NFTs bridged from Flow EVM to Flow. /// Upon deployment of this contract, the contract name is derived as a function of the asset type (here an ERC721 aka diff --git a/cadence/contracts/templates/testing/EVMBridgedTokenTemplate.cdc b/cadence/contracts/templates/testing/EVMBridgedTokenTemplate.cdc index fe64ad96..9527e955 100644 --- a/cadence/contracts/templates/testing/EVMBridgedTokenTemplate.cdc +++ b/cadence/contracts/templates/testing/EVMBridgedTokenTemplate.cdc @@ -1,21 +1,21 @@ -import NonFungibleToken from 0x0000000000000001 -import MetadataViews from 0x0000000000000001 -import FungibleTokenMetadataViews from 0x0000000000000002 -import ViewResolver from 0x0000000000000001 -import FungibleToken from 0x0000000000000002 -import FlowToken from 0x0000000000000003 +import "NonFungibleToken" +import "MetadataViews" +import "FungibleTokenMetadataViews" +import "ViewResolver" +import "FungibleToken" +import "FlowToken" -import EVM from 0x0000000000000001 +import "EVM" -import ICrossVM from 0x0000000000000007 -import ICrossVMAsset from 0x0000000000000007 -import IEVMBridgeTokenMinter from 0x0000000000000007 -import FlowEVMBridgeTokenEscrow from 0x0000000000000007 -import FlowEVMBridgeConfig from 0x0000000000000007 -import FlowEVMBridgeUtils from 0x0000000000000007 -import FlowEVMBridge from 0x0000000000000007 -import CrossVMToken from 0x0000000000000007 -import FlowEVMBridgeResolver from 0x0000000000000007 +import "ICrossVM" +import "ICrossVMAsset" +import "IEVMBridgeTokenMinter" +import "FlowEVMBridgeTokenEscrow" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +import "CrossVMToken" +import "FlowEVMBridgeResolver" /// This contract is a template used by FlowEVMBridge to define EVM-native fungible tokens bridged from Flow EVM to /// Cadence. Upon deployment of this contract, the contract name is derived as a function of the asset type (here an diff --git a/cadence/contracts/templates/testnet/EVMBridgedNFTTemplate.cdc b/cadence/contracts/templates/testnet/EVMBridgedNFTTemplate.cdc index cd8e2128..6b9744e0 100644 --- a/cadence/contracts/templates/testnet/EVMBridgedNFTTemplate.cdc +++ b/cadence/contracts/templates/testnet/EVMBridgedNFTTemplate.cdc @@ -1,20 +1,20 @@ -import NonFungibleToken from 0x631e88ae7f1d7c20 -import MetadataViews from 0x631e88ae7f1d7c20 -import ViewResolver from 0x631e88ae7f1d7c20 -import FungibleToken from 0x9a0766d93b6608b7 -import FlowToken from 0x7e60df042a9c0868 - -import EVM from 0x8c5303eaa26202d6 - -import ICrossVM from 0xdfc20aee650fcbdf -import ICrossVMAsset from 0xdfc20aee650fcbdf -import IEVMBridgeNFTMinter from 0xdfc20aee650fcbdf -import FlowEVMBridgeNFTEscrow from 0xdfc20aee650fcbdf -import FlowEVMBridgeConfig from 0xdfc20aee650fcbdf -import FlowEVMBridgeUtils from 0xdfc20aee650fcbdf -import FlowEVMBridge from 0xdfc20aee650fcbdf -import CrossVMNFT from 0xdfc20aee650fcbdf -import FlowEVMBridgeResolver from 0xdfc20aee650fcbdf +import "NonFungibleToken" +import "MetadataViews" +import "ViewResolver" +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "ICrossVM" +import "ICrossVMAsset" +import "IEVMBridgeNFTMinter" +import "FlowEVMBridgeNFTEscrow" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +import "CrossVMNFT" +import "FlowEVMBridgeResolver" /// This contract is a template used by FlowEVMBridge to define EVM-native NFTs bridged from Flow EVM to Flow. /// Upon deployment of this contract, the contract name is derived as a function of the asset type (here an ERC721 aka diff --git a/cadence/contracts/templates/testnet/EVMBridgedTokenTemplate.cdc b/cadence/contracts/templates/testnet/EVMBridgedTokenTemplate.cdc index 42f76ac2..9527e955 100644 --- a/cadence/contracts/templates/testnet/EVMBridgedTokenTemplate.cdc +++ b/cadence/contracts/templates/testnet/EVMBridgedTokenTemplate.cdc @@ -1,21 +1,21 @@ -import NonFungibleToken from 0x631e88ae7f1d7c20 -import MetadataViews from 0x631e88ae7f1d7c20 -import FungibleTokenMetadataViews from 0x9a0766d93b6608b7 -import ViewResolver from 0x631e88ae7f1d7c20 -import FungibleToken from 0x9a0766d93b6608b7 -import FlowToken from 0x7e60df042a9c0868 +import "NonFungibleToken" +import "MetadataViews" +import "FungibleTokenMetadataViews" +import "ViewResolver" +import "FungibleToken" +import "FlowToken" -import EVM from 0x8c5303eaa26202d6 +import "EVM" -import ICrossVM from 0xdfc20aee650fcbdf -import ICrossVMAsset from 0xdfc20aee650fcbdf -import IEVMBridgeTokenMinter from 0xdfc20aee650fcbdf -import FlowEVMBridgeTokenEscrow from 0xdfc20aee650fcbdf -import FlowEVMBridgeConfig from 0xdfc20aee650fcbdf -import FlowEVMBridgeUtils from 0xdfc20aee650fcbdf -import FlowEVMBridge from 0xdfc20aee650fcbdf -import CrossVMToken from 0xdfc20aee650fcbdf -import FlowEVMBridgeResolver from 0xdfc20aee650fcbdf +import "ICrossVM" +import "ICrossVMAsset" +import "IEVMBridgeTokenMinter" +import "FlowEVMBridgeTokenEscrow" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "FlowEVMBridge" +import "CrossVMToken" +import "FlowEVMBridgeResolver" /// This contract is a template used by FlowEVMBridge to define EVM-native fungible tokens bridged from Flow EVM to /// Cadence. Upon deployment of this contract, the contract name is derived as a function of the asset type (here an