From 05062f6b1bf966d1c41bc7ec05e43db6eb63d0ad Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 30 Aug 2024 19:27:15 -0600 Subject: [PATCH 1/4] add metadata discovery & resolution paths to escrow contracts --- .../bridge/FlowEVMBridgeNFTEscrow.cdc | 32 +++++++++++++++---- .../bridge/FlowEVMBridgeTokenEscrow.cdc | 16 ++++++---- .../escrow/get_locked_token_balance.cdc | 11 +++++++ cadence/scripts/escrow/get_nft_views.cdc | 16 ++++++++++ cadence/scripts/escrow/get_vault_views.cdc | 16 ++++++++++ .../{is_locked.cdc => is_nft_locked.cdc} | 2 +- .../escrow/resolve_locked_nft_metadata.cdc | 4 +-- .../escrow/resolve_locked_vault_metadata.cdc | 19 +++++++++++ cadence/tests/flow_evm_bridge_tests.cdc | 10 ++++++ cadence/tests/test_helpers.cdc | 20 ++++++++++++ 10 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 cadence/scripts/escrow/get_locked_token_balance.cdc create mode 100644 cadence/scripts/escrow/get_nft_views.cdc create mode 100644 cadence/scripts/escrow/get_vault_views.cdc rename cadence/scripts/escrow/{is_locked.cdc => is_nft_locked.cdc} (90%) create mode 100644 cadence/scripts/escrow/resolve_locked_vault_metadata.cdc diff --git a/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc b/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc index f8de4b72..d5ce6cc3 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc @@ -63,6 +63,20 @@ access(all) contract FlowEVMBridgeNFTEscrow { return self.borrowLocker(forType: type)?.getEVMID(from: cadenceID) ?? nil } + /// Returns the metadata view types supported by a given NFT if it is in escrow, nil otherwise + /// + /// @param nftType: Type of the locked NFT + /// @param id: ID of the locked NFT + /// + /// @returns The metadata view types supported by the locked NFT if it is in escrow, nil otherwise + /// + access(all) view fun getViews(nftType: Type, id: UInt64): [Type]? { + if let nft = self.borrowLockedNFT(type: nftType, id: id) { + return nft.getViews() + } + return nil + } + /// Resolves the requested view type for the given NFT type if it is locked and supports the requested view type /// /// @param nftType: Type of the locked NFT @@ -88,9 +102,9 @@ access(all) contract FlowEVMBridgeNFTEscrow { /// access(account) fun initializeEscrow(forType: Type, name: String, symbol: String, erc721Address: EVM.EVMAddress) { let lockerPath = FlowEVMBridgeUtils.deriveEscrowStoragePath(fromType: forType) - ?? panic("Problem deriving locker path") + ?? panic("Problem deriving Locker path for NFT type identifier=".concat(forType.identifier)) if self.account.storage.type(at: lockerPath) != nil { - panic("Locker already stored at storage path: ".concat(lockerPath.toString())) + panic("NFT Locker already stored at storage path=".concat(lockerPath.toString())) } let locker <- create Locker(name: name, symbol: symbol, lockedType: forType, erc721Address: erc721Address) @@ -101,7 +115,7 @@ access(all) contract FlowEVMBridgeNFTEscrow { /// access(account) fun lockNFT(_ nft: @{NonFungibleToken.NFT}): UInt64 { let locker = self.borrowLocker(forType: nft.getType()) - ?? panic("Problem deriving locker path") + ?? panic("Problem borrowing reference to Locker for NFT type identifier=".concat(nft.getType().identifier)) let preStorageSnapshot = self.account.storage.used locker.deposit(token: <-nft) @@ -114,7 +128,7 @@ access(all) contract FlowEVMBridgeNFTEscrow { /// access(account) fun unlockNFT(type: Type, id: UInt64): @{NonFungibleToken.NFT} { let locker = self.borrowLocker(forType: type) - ?? panic("Problem deriving locker path") + ?? panic("Problem borrowing reference to Locker for NFT type identifier=".concat(type.identifier)) return <- locker.withdraw(withdrawID: id) } @@ -270,7 +284,9 @@ access(all) contract FlowEVMBridgeNFTEscrow { access(all) fun deposit(token: @{NonFungibleToken.NFT}) { pre { - self.borrowNFT(token.id) == nil: "NFT with this ID already exists in the Locker" + self.borrowNFT(token.id) == nil: + "NFT type=".concat(token.getType().identifier).concat(" with id=").concat(token.id.toString()) + .concat(" already exists in the Locker") } if let evmID = CrossVMNFT.getEVMID(from: &token as &{NonFungibleToken.NFT}) { self.evmIDToFlowID[evmID] = token.id @@ -284,7 +300,11 @@ access(all) contract FlowEVMBridgeNFTEscrow { access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} { // Should not happen, but prevent potential underflow - assert(self.lockedNFTCount > 0, message: "No NFTs to withdraw") + assert( + self.lockedNFTCount > 0, + message: "Attempting to withdraw NFT id=".concat(withdrawID.toString()) + .concat(" - no NFTs of type=").concat(self.lockedType.identifier).concat(" to withdraw") + ) self.lockedNFTCount = self.lockedNFTCount - 1 let token <- self.ownedNFTs.remove(key: withdrawID)! if let evmID = CrossVMNFT.getEVMID(from: &token as &{NonFungibleToken.NFT}) { diff --git a/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc b/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc index a8c9eebf..a16af110 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc @@ -77,13 +77,15 @@ access(all) contract FlowEVMBridgeTokenEscrow { evmTokenAddress: EVM.EVMAddress ) { pre { - vault.balance == 0.0: "Can only initialize Escrow with an empty vault" + vault.balance == 0.0: + "Vault contains a balance=".concat(vault.balance.toString()) + .concat(" - can only initialize Escrow with an empty vault") } let lockedType = vault.getType() let lockerPath = FlowEVMBridgeUtils.deriveEscrowStoragePath(fromType: lockedType) - ?? panic("Problem deriving locker path") + ?? panic("Problem deriving Locker path for Vault type identifier=".concat(lockedType.identifier)) if self.account.storage.type(at: lockerPath) != nil { - panic("Collision at derived Locker path for type: ".concat(lockedType.identifier)) + panic("Token Locker already stored at storage path=".concat(lockedType.identifier)) } // Create the Locker, lock a new vault of given type and save at the derived path @@ -100,7 +102,8 @@ access(all) contract FlowEVMBridgeTokenEscrow { /// Locks the fungible tokens in escrow returning the storage used by locking the Vault /// access(account) fun lockTokens(_ vault: @{FungibleToken.Vault}): UInt64 { - let locker = self.borrowLocker(forType: vault.getType()) ?? panic("Locker doesn't exist for given type") + let locker = self.borrowLocker(forType: vault.getType()) + ?? panic("Locker doesn't exist for given type=".concat(vault.getType().identifier)) let preStorageSnapshot = self.account.storage.used locker.deposit(from: <-vault) @@ -112,7 +115,8 @@ access(all) contract FlowEVMBridgeTokenEscrow { /// Unlocks the tokens of the given type and amount, reverting if it isn't in escrow /// access(account) fun unlockTokens(type: Type, amount: UFix64): @{FungibleToken.Vault} { - let locker = self.borrowLocker(forType: type) ?? panic("Locker doesn't exist for given type") + let locker = self.borrowLocker(forType: type) + ?? panic("Locker doesn't exist for given type=".concat(type.identifier)) return <- locker.withdraw(amount: amount) } @@ -158,7 +162,7 @@ access(all) contract FlowEVMBridgeTokenEscrow { // Locked Vaults must accept their own type as Lockers escrow Vaults on a 1:1 type basis assert( self.lockedVault.isSupportedVaultType(type: self.lockedVault.getType()), - message: "Locked Vault does not accept its own type" + message: "Locked Vault does not accept its own type=".concat(self.lockedVault.getType().identifier) ) } diff --git a/cadence/scripts/escrow/get_locked_token_balance.cdc b/cadence/scripts/escrow/get_locked_token_balance.cdc new file mode 100644 index 00000000..170e1f61 --- /dev/null +++ b/cadence/scripts/escrow/get_locked_token_balance.cdc @@ -0,0 +1,11 @@ +import "FlowEVMBridgeTokenEscrow" + +/// Returns the balance of a given FungibleToken Vault type locked in escrow or nil if a vault of the given type is not +/// locked in escrow +/// +/// @param vaultTypeIdentifier: The type identifier of the FungibleToken Vault +/// +access(all) fun main(vaultTypeIdentifier: String): UFix64? { + let tokenType = CompositeType(vaultTypeIdentifier) ?? panic("Malformed Vault type identifier=".concat(vaultTypeIdentifier)) + return FlowEVMBridgeTokenEscrow.getLockedTokenBalance(tokenType: tokenType) +} diff --git a/cadence/scripts/escrow/get_nft_views.cdc b/cadence/scripts/escrow/get_nft_views.cdc new file mode 100644 index 00000000..5d3a14f6 --- /dev/null +++ b/cadence/scripts/escrow/get_nft_views.cdc @@ -0,0 +1,16 @@ +import "NonFungibleToken" + +import "FlowEVMBridgeNFTEscrow" +import "FlowEVMBridge" + +/// Returns the views supported by an escrowed NFT or nil if the NFT is not locked in escrow +/// +/// @param nftTypeIdentifier: The type identifier of the NFT +/// @param id: The ID of the NFT +/// +/// @return The metadata view types supported by the escrowed NFT or nil if the NFT is not locked in escrow +/// +access(all) fun main(nftTypeIdentifier: String, id: UInt64): [Type]? { + let type = CompositeType(nftTypeIdentifier) ?? panic("Malformed NFT type identifier=".concat(nftTypeIdentifier)) + return FlowEVMBridgeNFTEscrow.getViews(nftType: type, id: id) +} diff --git a/cadence/scripts/escrow/get_vault_views.cdc b/cadence/scripts/escrow/get_vault_views.cdc new file mode 100644 index 00000000..a7e50e9e --- /dev/null +++ b/cadence/scripts/escrow/get_vault_views.cdc @@ -0,0 +1,16 @@ +import "NonFungibleToken" + +import "FlowEVMBridgeTokenEscrow" +import "FlowEVMBridge" + +/// Returns the views supported by an escrowed FungibleToken Vault or nil if there is no Vault of the given type locked +/// in escrow +/// +/// @param vaultTypeIdentifier: The type identifier of the NFT +/// +/// @return The metadata view types supported by the escrowed FT Vault or nil if there is not Vault locked in escrow +/// +access(all) fun main(vaultTypeIdentifier: String, id: UInt64): [Type]? { + let type = CompositeType(vaultTypeIdentifier) ?? panic("Malformed Vault type identifier=".concat(vaultTypeIdentifier)) + return FlowEVMBridgeTokenEscrow.getViews(tokenType: type) +} diff --git a/cadence/scripts/escrow/is_locked.cdc b/cadence/scripts/escrow/is_nft_locked.cdc similarity index 90% rename from cadence/scripts/escrow/is_locked.cdc rename to cadence/scripts/escrow/is_nft_locked.cdc index 6db07dad..b45934f1 100644 --- a/cadence/scripts/escrow/is_locked.cdc +++ b/cadence/scripts/escrow/is_nft_locked.cdc @@ -11,6 +11,6 @@ import "FlowEVMBridge" /// @return true if the NFT is locked in escrow and false otherwise /// access(all) fun main(nftTypeIdentifier: String, id: UInt64): Bool { - let type = CompositeType(nftTypeIdentifier) ?? panic("Malformed type identifier") + let type = CompositeType(nftTypeIdentifier) ?? panic("Malformed NFT type identifier=".concat(nftTypeIdentifier)) return FlowEVMBridgeNFTEscrow.isLocked(type: type, id: id) } diff --git a/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc b/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc index d29677e8..bc333fd2 100644 --- a/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc +++ b/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc @@ -13,8 +13,8 @@ import "FlowEVMBridge" /// @return The resolved view if the NFT is escrowed & the view is resolved by it or nil if the NFT is not locked /// access(all) fun main(nftTypeIdentifier: String, id: UInt256, viewIdentifier: String): AnyStruct? { - let nftType: Type = CompositeType(nftTypeIdentifier) ?? panic("Malformed nft type identifier") - let view: Type = CompositeType(viewIdentifier) ?? panic("Malformed view type identifier") + let nftType: Type = CompositeType(nftTypeIdentifier) ?? panic("Malformed NFT type identifier=".concat(nftTypeIdentifier)) + let view: Type = CompositeType(viewIdentifier) ?? panic("Malformed view type identifier=".concat(viewIdentifier)) return FlowEVMBridgeNFTEscrow.resolveLockedNFTView(nftType: nftType, id: id, viewType: view) } diff --git a/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc b/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc new file mode 100644 index 00000000..38c95229 --- /dev/null +++ b/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc @@ -0,0 +1,19 @@ +import "NonFungibleToken" +import "MetadataViews" + +import "FlowEVMBridgeTokenEscrow" +import "FlowEVMBridge" + +/// Resolves the view for the requested locked Vault or nil if the Vault is not locked in escrow +/// +/// @param vaultTypeIdentifier: The identifier of the Vault type +/// @param viewIdentifier: The identifier of the view to resolve +/// +/// @return The resolved view if the Vault is escrowed & the view is resolved by it or nil if the Vault is not locked +/// +access(all) fun main(vaultTypeIdentifier: String, viewIdentifier: String): AnyStruct? { + let vaultType: Type = CompositeType(vaultTypeIdentifier) ?? panic("Malformed vault type identifier=".concat(vaultTypeIdentifier)) + let view: Type = CompositeType(viewIdentifier) ?? panic("Malformed view type identifier=".concat(viewIdentifier)) + + return FlowEVMBridgeTokenEscrow.resolveLockedTokenView(tokenType: vaultType, viewType: view) +} diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index 0a7748e2..c4b5455b 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -1053,6 +1053,9 @@ fun testBridgeCadenceNativeNFTToEVMSucceeds() { ) Test.expect(isOwnerResult, Test.beSucceeded()) Test.assertEqual(true, isOwnerResult.returnValue as! Bool? ?? panic("Problem getting owner status")) + + let isNFTLocked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID) + Test.assertEqual(true, isNFTLocked) } access(all) @@ -1091,6 +1094,9 @@ fun testCrossVMTransferCadenceNativeNFTFromEVMSucceeds() { let bobOwnedIDs = getIDs(ownerAddr: bob.address, storagePathIdentifier: "cadenceExampleNFTCollection") Test.assertEqual(1, bobOwnedIDs.length) Test.assertEqual(mintedNFTID, bobOwnedIDs[0]) + + let isNFTLocked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID) + Test.assertEqual(false, isNFTLocked) } access(all) @@ -1260,6 +1266,10 @@ fun testBridgeCadenceNativeTokenToEVMSucceeds() { let expectedEVMBalance = ufix64ToUInt256(exampleTokenMintAmount, decimals: decimals) let evmBalance = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: associatedEVMAddressHex) Test.assertEqual(expectedEVMBalance, evmBalance) + + // Confirm the token is locked + let lockedBalance = getLockedTokenBalance(vaultTypeIdentifier: exampleTokenIdentifier) ?? panic("Problem getting locked balance") + Test.assertEqual(exampleTokenMintAmount, lockedBalance) } access(all) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index cd586013..4ded251a 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -351,6 +351,26 @@ fun evmAddressRequiresOnboarding(_ addressHex: String): Bool? { return onboardingRequiredResult.returnValue as! Bool? ?? panic("Problem getting onboarding requirement") } +access(all) +fun isNFTLocked(nftTypeIdentifier: String, id: UInt64): Bool { + let isLockedResult = _executeScript( + "../scripts/escrow/is_nft_locked.cdc", + [nftTypeIdentifier, id] + ) + Test.expect(isLockedResult, Test.beSucceeded()) + return isLockedResult.returnValue as! Bool? ?? panic("Problem getting locked status") +} + +access(all) +fun getLockedTokenBalance(vaultTypeIdentifier: String): UFix64? { + let balanceResult = _executeScript( + "../scripts/escrow/get_locked_token_balance.cdc", + [vaultTypeIdentifier] + ) + Test.expect(balanceResult, Test.beSucceeded()) + return balanceResult.returnValue as! UFix64? +} + /* --- Transaction Helpers --- */ access(all) From a8617f48bd14a6cfdde888d2389ffbd0e576e83f Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Sun, 1 Sep 2024 15:51:00 -0600 Subject: [PATCH 2/4] remove resolveLockedNFTView & add script for MD resolution --- .../bridge/FlowEVMBridgeNFTEscrow.cdc | 17 ------------ .../escrow/resolve_locked_nft_metadata.cdc | 27 ++++++++++++++++--- cadence/tests/flow_evm_bridge_tests.cdc | 4 +++ cadence/tests/test_helpers.cdc | 10 +++++++ 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc b/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc index d5ce6cc3..4be73e3b 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc @@ -77,23 +77,6 @@ access(all) contract FlowEVMBridgeNFTEscrow { return nil } - /// Resolves the requested view type for the given NFT type if it is locked and supports the requested view type - /// - /// @param nftType: Type of the locked NFT - /// @param viewType: Type of the view to resolve - /// @param id: ID of the locked NFT - /// - /// @returns The resolved view as AnyStruct if the NFT is locked and the view is supported, otherwise returns nil - /// - access(all) fun resolveLockedNFTView(nftType: Type, id: UInt256, viewType: Type): AnyStruct? { - if let locker = self.borrowLocker(forType: nftType) { - if let cadenceID = locker.getCadenceID(from: id) { - return locker.borrowViewResolver(id: cadenceID)?.resolveView(viewType) ?? nil - } - } - return nil - } - /********************** Bridge Methods ***********************/ diff --git a/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc b/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc index bc333fd2..5611da59 100644 --- a/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc +++ b/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc @@ -2,19 +2,40 @@ import "NonFungibleToken" import "MetadataViews" import "FlowEVMBridgeNFTEscrow" -import "FlowEVMBridge" +import "FlowEVMBridgeUtils" /// Resolves the view for the requested locked NFT or nil if the NFT is not locked +/// NOTE: This functionality is not available via the escrow contract as `resolveView` is not a `view` method, but the +/// escrow contract /// +/// @param bridgeAddress: The address of the bridge contract /// @param nftTypeIdentifier: The identifier of the NFT type /// @param id: The ERC721 id of the escrowed NFT /// @param viewIdentifier: The identifier of the view to resolve /// /// @return The resolved view if the NFT is escrowed & the view is resolved by it or nil if the NFT is not locked /// -access(all) fun main(nftTypeIdentifier: String, id: UInt256, viewIdentifier: String): AnyStruct? { +access(all) fun main(bridgeAddress: Address, nftTypeIdentifier: String, id: UInt256, viewIdentifier: String): AnyStruct? { + // Construct runtime types from provided identifiers let nftType: Type = CompositeType(nftTypeIdentifier) ?? panic("Malformed NFT type identifier=".concat(nftTypeIdentifier)) let view: Type = CompositeType(viewIdentifier) ?? panic("Malformed view type identifier=".concat(viewIdentifier)) - return FlowEVMBridgeNFTEscrow.resolveLockedNFTView(nftType: nftType, id: id, viewType: view) + // Derive the Locker path for the given NFT type + let lockerPath = FlowEVMBridgeUtils.deriveEscrowStoragePath(fromType: nftType) + ?? panic("Problem deriving Locker path for NFT type identifier=".concat(nftTypeIdentifier)) + + // Borrow the locker from the bridge account's storage + if let locker = getAuthAccount(bridgeAddress).storage.borrow<&FlowEVMBridgeNFTEscrow.Locker>( + from: lockerPath + ) { + // Retrieve the NFT type's cadence ID from the locker + if let cadenceID = locker.getCadenceID(from: id) { + // Resolve the requested view for the given NFT type, returning nil if the view is not supported or the NFT + // is not locked in escrow + return locker.borrowViewResolver(id: cadenceID)?.resolveView(view) + } + } + + // Return nil if no locker was found for the given NFT type + return nil } diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index c4b5455b..fb52ac61 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -3,6 +3,7 @@ import BlockchainHelpers import "FungibleToken" import "NonFungibleToken" +import "MetadataViews" import "ExampleNFT" import "ExampleToken" import "FlowStorageFees" @@ -1056,6 +1057,9 @@ fun testBridgeCadenceNativeNFTToEVMSucceeds() { let isNFTLocked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID) Test.assertEqual(true, isNFTLocked) + + let metadata = resolveLockedNFTMetadata(bridgeAddress: bridgeAccount.address, nftTypeIdentifier: exampleNFTIdentifier, id: UInt256(mintedNFTID), viewIdentifier: Type().identifier) + Test.assert(metadata != nil, message: "Expected metadata to be resolved") } access(all) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 4ded251a..e719b688 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -371,6 +371,16 @@ fun getLockedTokenBalance(vaultTypeIdentifier: String): UFix64? { return balanceResult.returnValue as! UFix64? } +access(all) +fun resolveLockedNFTMetadata(bridgeAddress: Address, nftTypeIdentifier: String, id: UInt256, viewIdentifier: String): AnyStruct? { + let resolvedViewResult = _executeScript( + "../scripts/escrow/resolve_locked_nft_metadata.cdc", + [bridgeAddress, nftTypeIdentifier, id, viewIdentifier] + ) + Test.expect(resolvedViewResult, Test.beSucceeded()) + return resolvedViewResult.returnValue as! AnyStruct? +} + /* --- Transaction Helpers --- */ access(all) From 93efc2435ca0fdb27578dd43e72bb9f1051a2dce Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Sun, 1 Sep 2024 16:01:07 -0600 Subject: [PATCH 3/4] remove resolveLockedTokenView & add script for MD resolution --- .../bridge/FlowEVMBridgeTokenEscrow.cdc | 12 ------------ .../escrow/resolve_locked_nft_metadata.cdc | 2 +- .../escrow/resolve_locked_vault_metadata.cdc | 19 ++++++++++++++++--- cadence/tests/flow_evm_bridge_tests.cdc | 8 ++++++-- cadence/tests/test_helpers.cdc | 12 +++++++++++- 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc b/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc index a16af110..a407b279 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeTokenEscrow.cdc @@ -51,18 +51,6 @@ access(all) contract FlowEVMBridgeTokenEscrow { return self.borrowLocker(forType: tokenType)?.getViews() ?? [] } - /// Resolves the requested view type for the given FT type if it is locked and supports the requested view type - /// - /// @param tokenType: Type of the locked fungible tokens - /// @param viewType: Type of the view to resolve - /// - /// @returns The resolved view as AnyStruct if the vault is locked and the view is supported, otherwise returns nil - /// - access(all) fun resolveLockedTokenView(tokenType: Type, viewType: Type): AnyStruct? { - // The Locker implements Resolver, which has basic resolveView functionality - return self.borrowLocker(forType: tokenType)?.resolveView(viewType) ?? nil - } - /********************** Bridge Methods ***********************/ diff --git a/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc b/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc index 5611da59..ffef4b01 100644 --- a/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc +++ b/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc @@ -8,7 +8,7 @@ import "FlowEVMBridgeUtils" /// NOTE: This functionality is not available via the escrow contract as `resolveView` is not a `view` method, but the /// escrow contract /// -/// @param bridgeAddress: The address of the bridge contract +/// @param bridgeAddress: The address of the bridge contract (included as the VM bridge address varies across networks) /// @param nftTypeIdentifier: The identifier of the NFT type /// @param id: The ERC721 id of the escrowed NFT /// @param viewIdentifier: The identifier of the view to resolve diff --git a/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc b/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc index 38c95229..af3f773a 100644 --- a/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc +++ b/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc @@ -2,18 +2,31 @@ import "NonFungibleToken" import "MetadataViews" import "FlowEVMBridgeTokenEscrow" -import "FlowEVMBridge" +import "FlowEVMBridgeUtils" /// Resolves the view for the requested locked Vault or nil if the Vault is not locked in escrow /// +/// @param bridgeAddress: The address of the bridge contract (included as the VM bridge address varies across networks) /// @param vaultTypeIdentifier: The identifier of the Vault type /// @param viewIdentifier: The identifier of the view to resolve /// /// @return The resolved view if the Vault is escrowed & the view is resolved by it or nil if the Vault is not locked /// -access(all) fun main(vaultTypeIdentifier: String, viewIdentifier: String): AnyStruct? { +access(all) fun main(bridgeAddress: Address, vaultTypeIdentifier: String, viewIdentifier: String): AnyStruct? { + // Construct runtime types from provided identifiers let vaultType: Type = CompositeType(vaultTypeIdentifier) ?? panic("Malformed vault type identifier=".concat(vaultTypeIdentifier)) let view: Type = CompositeType(viewIdentifier) ?? panic("Malformed view type identifier=".concat(viewIdentifier)) - return FlowEVMBridgeTokenEscrow.resolveLockedTokenView(tokenType: vaultType, viewType: view) + // Derive the Locker path for the given Vault type + let lockerPath = FlowEVMBridgeUtils.deriveEscrowStoragePath(fromType: vaultType) + ?? panic("Problem deriving Locker path for NFT type identifier=".concat(vaultTypeIdentifier)) + + // Borrow the locker from the bridge account's storage & return the requested view if the locker exists + if let locker = getAuthAccount(bridgeAddress).storage.borrow<&FlowEVMBridgeTokenEscrow.Locker>( + from: lockerPath + ) { + return locker.resolveView(view) + } + + return nil } diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index fb52ac61..25c02f24 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -4,6 +4,7 @@ import BlockchainHelpers import "FungibleToken" import "NonFungibleToken" import "MetadataViews" +import "FungibleTokenMetadataViews" import "ExampleNFT" import "ExampleToken" import "FlowStorageFees" @@ -1058,8 +1059,8 @@ fun testBridgeCadenceNativeNFTToEVMSucceeds() { let isNFTLocked = isNFTLocked(nftTypeIdentifier: exampleNFTIdentifier, id: mintedNFTID) Test.assertEqual(true, isNFTLocked) - let metadata = resolveLockedNFTMetadata(bridgeAddress: bridgeAccount.address, nftTypeIdentifier: exampleNFTIdentifier, id: UInt256(mintedNFTID), viewIdentifier: Type().identifier) - Test.assert(metadata != nil, message: "Expected metadata to be resolved") + let metadata = resolveLockedNFTView(bridgeAddress: bridgeAccount.address, nftTypeIdentifier: exampleNFTIdentifier, id: UInt256(mintedNFTID), viewIdentifier: Type().identifier) + Test.assert(metadata != nil, message: "Expected NFT metadata to be resolved from escrow but none was returned") } access(all) @@ -1274,6 +1275,9 @@ fun testBridgeCadenceNativeTokenToEVMSucceeds() { // Confirm the token is locked let lockedBalance = getLockedTokenBalance(vaultTypeIdentifier: exampleTokenIdentifier) ?? panic("Problem getting locked balance") Test.assertEqual(exampleTokenMintAmount, lockedBalance) + + let metadata = resolveLockedTokenView(bridgeAddress: bridgeAccount.address, vaultTypeIdentifier: exampleTokenIdentifier, viewIdentifier: Type().identifier) + Test.assert(metadata != nil, message: "Expected Vault metadata to be resolved from escrow but none was returned") } access(all) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index e719b688..307a1efb 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -372,7 +372,7 @@ fun getLockedTokenBalance(vaultTypeIdentifier: String): UFix64? { } access(all) -fun resolveLockedNFTMetadata(bridgeAddress: Address, nftTypeIdentifier: String, id: UInt256, viewIdentifier: String): AnyStruct? { +fun resolveLockedNFTView(bridgeAddress: Address, nftTypeIdentifier: String, id: UInt256, viewIdentifier: String): AnyStruct? { let resolvedViewResult = _executeScript( "../scripts/escrow/resolve_locked_nft_metadata.cdc", [bridgeAddress, nftTypeIdentifier, id, viewIdentifier] @@ -381,6 +381,16 @@ fun resolveLockedNFTMetadata(bridgeAddress: Address, nftTypeIdentifier: String, return resolvedViewResult.returnValue as! AnyStruct? } +access(all) +fun resolveLockedTokenView(bridgeAddress: Address, vaultTypeIdentifier: String, viewIdentifier: String): AnyStruct? { + let resolvedViewResult = _executeScript( + "../scripts/escrow/resolve_locked_vault_metadata.cdc", + [bridgeAddress, vaultTypeIdentifier, viewIdentifier] + ) + Test.expect(resolvedViewResult, Test.beSucceeded()) + return resolvedViewResult.returnValue as! AnyStruct? +} + /* --- Transaction Helpers --- */ access(all) From db653969aff91bd16f92c363aaf8b12e5dc6907b Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 2 Sep 2024 12:33:10 -0600 Subject: [PATCH 4/4] update scipt comments --- cadence/scripts/escrow/resolve_locked_nft_metadata.cdc | 2 +- cadence/scripts/escrow/resolve_locked_vault_metadata.cdc | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc b/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc index ffef4b01..cc7a2bf2 100644 --- a/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc +++ b/cadence/scripts/escrow/resolve_locked_nft_metadata.cdc @@ -6,7 +6,7 @@ import "FlowEVMBridgeUtils" /// Resolves the view for the requested locked NFT or nil if the NFT is not locked /// NOTE: This functionality is not available via the escrow contract as `resolveView` is not a `view` method, but the -/// escrow contract +/// escrow contract does provide the necessary functionality to resolve the view from the context of a script /// /// @param bridgeAddress: The address of the bridge contract (included as the VM bridge address varies across networks) /// @param nftTypeIdentifier: The identifier of the NFT type diff --git a/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc b/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc index af3f773a..9c4808ea 100644 --- a/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc +++ b/cadence/scripts/escrow/resolve_locked_vault_metadata.cdc @@ -5,6 +5,8 @@ import "FlowEVMBridgeTokenEscrow" import "FlowEVMBridgeUtils" /// Resolves the view for the requested locked Vault or nil if the Vault is not locked in escrow +/// NOTE: This functionality is not available via the escrow contract as `resolveView` is not a `view` method, but the +/// escrow contract does provide the necessary functionality to resolve the view from the context of a script /// /// @param bridgeAddress: The address of the bridge contract (included as the VM bridge address varies across networks) /// @param vaultTypeIdentifier: The identifier of the Vault type