Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CrossVM views #243

Merged
merged 9 commits into from
Feb 12, 2025
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
.vscode
node_modules
coverage.json
*.pkey
*.pkey
imports/
coverage.lcov
coverage.json
lcov.info
83 changes: 83 additions & 0 deletions contracts/CrossVMMetadataViews.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import "ViewResolver"
import "EVM"

/// This contract implements views originally proposed in FLIP-318 supporting NFT collections
/// with project-defined implementations across both Cadence & EVM.
/// The View structs in this contract should be implemented in the same way that views from `MetadataViews` are implemented
///
access(all) contract CrossVMMetadataViews {

/// An enum denoting a VM. For now, there are only two VMs on Flow, but this enum could be
/// expanded in the event other VMs are supported on the network.
///
access(all) enum VM : UInt8 {
access(all) case Cadence
access(all) case EVM
}

/// View resolved at contract & resource level pointing to the associated EVM implementation.
/// NOTE: This view alone is not sufficient to validate an association across Cadence & EVM!
/// Both the Cadence Type/contract *and* the EVM contract should point to each other, with the
/// EVM pointer being facilitated by ICrossVM.sol contract interface methods. For more
/// information and context, see FLIP-318: https://github.com/onflow/flips/issues/318
///
access(all) struct EVMPointer {
/// The associated Cadence Type defined in the contract that this view is returned from
access(all) let cadenceType: Type
/// The defining Cadence contract address
access(all) let cadenceContractAddress: Address
/// The associated EVM contract address that the Cadence contract will bridge to
access(all) let evmContractAddress: EVM.EVMAddress
/// Whether the asset is Cadence- or EVM-native. Native here meaning the VM in which the
/// asset is originally distributed.
access(all) let nativeVM: VM

init(
cadenceType: Type,
cadenceContractAddress: Address,
evmContractAddress: EVM.EVMAddress,
nativeVM: VM
) {
self.cadenceType = cadenceType
self.cadenceContractAddress = cadenceContractAddress
self.evmContractAddress = evmContractAddress
self.nativeVM = nativeVM
}
}

access(all) fun getEVMPointer(_ viewResolver: &{ViewResolver.Resolver}): EVMPointer? {
if let view = viewResolver.resolveView(Type<EVMPointer>()) {
if let v = view as? EVMPointer {
return v
}
}
return nil
}

/// View resolved at resource level denoting any metadata to be passed to the associated EVM
/// contract at the time of bridging. This optional view would allow EVM side metadata to be
/// updated based on current Cadence state. If the view is not supported, no bytes will be
/// passed into EVM when bridging.
///
access(all) struct EVMBytesMetadata {
/// Returns the bytes to be passed to the EVM contract on `fulfillToEVM` call, allowing the
/// EVM contract to update the metadata associated with the NFT. The corresponding Solidity
/// `bytes` type allows the implementer greater flexibility by enabling them to pass
/// arbitrary data between VMs.
access(all) let bytes: EVM.EVMBytes

init(bytes: EVM.EVMBytes) {
self.bytes = bytes
}
}

access(all) fun getEVMBytesMetadata(_ viewResolver: &{ViewResolver.Resolver}): EVMBytesMetadata? {
if let view = viewResolver.resolveView(Type<EVMBytesMetadata>()) {
if let v = view as? EVMBytesMetadata {
return v
}
}
return nil
}

}
51 changes: 51 additions & 0 deletions contracts/ExampleNFT.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import "NonFungibleToken"
import "ViewResolver"
import "MetadataViews"
import "CrossVMMetadataViews"
import "EVM"

access(all) contract ExampleNFT: NonFungibleToken {

Expand Down Expand Up @@ -127,6 +129,11 @@ access(all) contract ExampleNFT: NonFungibleToken {
case Type<MetadataViews.EVMBridgedMetadata>():
// Implementing this view gives the project control over how the bridged NFT is represented as an
// ERC721 when bridged to EVM on Flow via the public infrastructure bridge.
// NOTE: If your NFT is a cross-VM NFT, meaning you control both your Cadence & EVM contracts and
// registered your custom association with the VM bridge, it's recommended you use the
// CrossVMMetadata.EVMBytesMetadata view to define and pass metadata as EVMBytes into your
// EVM contract at the time of bridging into EVM. For more information about cross-VM NFTs,
// see FLIP-318: https://github.com/onflow/flips/issues/318

// Get the contract-level name and symbol values
let contractLevel = ExampleNFT.resolveContractView(
Expand All @@ -152,6 +159,29 @@ access(all) contract ExampleNFT: NonFungibleToken {
} else {
return nil
}
case Type<CrossVMMetadataViews.EVMPointer>():
// This view is intended for NFT projects with corresponding NFT implementations in both Cadence and
// EVM. Resolving EVMPointer indicates the associated EVM implementation. Fully validating the
// cross-VM association would involve inspecting the associated EVM contract and ensuring that
// contract also points to the resolved Cadence type and contract address. For more information
// about cross-VM NFTs, see FLIP-318: https://github.com/onflow/flips/issues/318

return ExampleNFT.resolveContractView(resourceType: self.getType(), viewType: view)
case Type<CrossVMMetadataViews.EVMBytesMetadata>():
// This view is intended for Cadence-native NFTs with corresponding ERC721 implementations. By
// resolving, you're able to pass arbitrary metadata into your EVM contract whenever an NFT is
// bridged which can be useful for Cadence NFTs with dynamic metadata values.
// See FLIP-318 for more information about cross-VM NFTs: https://github.com/onflow/flips/issues/318

// Here we encoded the EVMBridgedMetadata URI and encode the string as EVM bytes, but you could pass any
// Cadence values that can be abi encoded and decode them in your EVM contract as you wish. Within
// your EVM contract, you can abi decode the bytes and update metadata in your ERC721 contract as
// you see fit.
let bridgedMetadata = (self.resolveView(Type<MetadataViews.EVMBridgedMetadata>()) as! MetadataViews.EVMBridgedMetadata?)!
let uri = bridgedMetadata.uri.uri()
let encodedURI = EVM.encodeABI([uri])
let evmBytes = EVM.EVMBytes(value: encodedURI)
return CrossVMMetadataViews.EVMBytesMetadata(bytes: evmBytes)
}
return nil
}
Expand Down Expand Up @@ -310,6 +340,27 @@ access(all) contract ExampleNFT: NonFungibleToken {
value: "https://example-nft.onflow.org/contract-metadata.json"
)
)
case Type<CrossVMMetadataViews.EVMPointer>():
// This view is intended for NFT projects with corresponding NFT implementations in both Cadence and
// EVM. Resolving EVMPointer indicates the associated EVM implementation. Fully validating the
// cross-VM association would involve inspecting the associated EVM contract and ensuring that contract
// also points to the resolved Cadence type and contract address. For more information about cross-VM
// NFTs, see FLIP-318: https://github.com/onflow/flips/issues/318

// Assigning a dummy EVM address and deserializing. Implementations would want to declare the actual
// EVM address corresponding to their corresponding ERC721. If using a proxy in your EVM contracts, this
// address should be your proxy's address.
let evmContractAddress = EVM.addressFromString(
"0x1234565789012345657890123456578901234565"
)
// Since this NFT is distributed in Cadence, it's declared as Cadence-native
let nativeVM = CrossVMMetadataViews.VM.Cadence
return CrossVMMetadataViews.EVMPointer(
cadenceType: Type<@ExampleNFT.NFT>(),
cadenceContractAddress: self.account.address,
evmContractAddress: evmContractAddress,
nativeVM: nativeVM
)
}
return nil
}
Expand Down
Loading
Loading