Skip to content

Commit

Permalink
address PR comments, update Cadence version, and add go contracts pac…
Browse files Browse the repository at this point in the history
…kage
  • Loading branch information
joshuahannan committed May 21, 2024
1 parent 97e3cfa commit aa171af
Show file tree
Hide file tree
Showing 21 changed files with 773 additions and 148 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ update-testnet:

.PHONY: test
test:
$(MAKE) generate -C lib/go
$(MAKE) test -C lib/go
flow-c1 test --cover --covercode="contracts" tests/*.cdc

.PHONY: ci
ci:
$(MAKE) ci -C lib/go
flow-c1 test --cover --covercode="contracts" tests/*.cdc
4 changes: 2 additions & 2 deletions contracts/NFTStorefront.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,7 @@ access(all) contract NFTStorefront {
///
access(all) fun borrowNFT(): &{NonFungibleToken.NFT}? {
let ref = self.nftProviderCapability.borrow()!.borrowNFT(self.getDetails().nftID)
//- CANNOT DO THIS IN PRECONDITION: "member of restricted type is not accessible: isInstance"
// result.isInstance(self.getDetails().nftType): "token has wrong type"
assert(ref != nil, message: "Could not borrow a reference to the NFT")
assert(ref!.isInstance(self.getDetails().nftType), message: "token has wrong type")
assert(ref?.id == self.getDetails().nftID, message: "token has wrong ID")
return (ref as &{NonFungibleToken.NFT}?)
Expand Down Expand Up @@ -337,6 +336,7 @@ access(all) contract NFTStorefront {

let nft = provider!.borrowNFT(self.details.nftID)
// This will precondition assert if the token is not available.
assert(nft != nil, message: "Could not borrow a reference to the NFT")
assert(nft!.isInstance(self.details.nftType), message: "token is not of specified type")
assert(nft?.id == self.details.nftID, message: "token does not have specified ID")
}
Expand Down
14 changes: 7 additions & 7 deletions contracts/NFTStorefrontV2.cdc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import FungibleToken from "./utility/FungibleToken.cdc"
import NonFungibleToken from "./utility/NonFungibleToken.cdc"
import "FungibleToken"
import "NonFungibleToken"

/// NFTStorefrontV2
///
Expand Down Expand Up @@ -300,8 +300,7 @@ access(all) contract NFTStorefrontV2 {
/// If it returns `false` then it means listing becomes ghost or sold out.
access(all) view fun hasListingBecomeGhosted(): Bool {
if let providerRef = self.nftProviderCapability.borrow() {
let availableIDs = providerRef.getIDs()
return availableIDs.contains(self.details.nftID)
return providerRef.borrowNFT(self.details.nftID) != nil
}
return false
}
Expand Down Expand Up @@ -566,14 +565,15 @@ access(all) contract NFTStorefrontV2 {
let collectionRef = nftProviderCapability.borrow()
?? panic("Could not borrow reference to collection")
let nftRef = collectionRef.borrowNFT(nftID)
?? panic("Could not borrow a reference to the desired NFT ID")

// Instead of letting an arbitrary value be set for the UUID of a given NFT, the contract
// should fetch it itself
let uuid = nftRef?.uuid
let uuid = nftRef.uuid
let listing <- create Listing(
nftProviderCapability: nftProviderCapability,
nftType: nftType,
nftUUID: uuid!,
nftUUID: uuid,
nftID: nftID,
salePaymentVaultType: salePaymentVaultType,
saleCuts: saleCuts,
Expand Down Expand Up @@ -610,7 +610,7 @@ access(all) contract NFTStorefrontV2 {
storefrontAddress: self.owner?.address!,
listingResourceID: listingResourceID,
nftType: nftType,
nftUUID: uuid!,
nftUUID: uuid,
nftID: nftID,
salePaymentVaultType: salePaymentVaultType,
salePrice: listingPrice,
Expand Down
49 changes: 45 additions & 4 deletions contracts/utility/ExampleNFT.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ access(all) contract ExampleNFT: NonFungibleToken {
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>(),
Type<MetadataViews.Serial>(),
Type<MetadataViews.Traits>()
Type<MetadataViews.Traits>(),
Type<MetadataViews.EVMBridgedMetadata>()
]
}

Expand Down Expand Up @@ -123,6 +124,31 @@ access(all) contract ExampleNFT: NonFungibleToken {
traitsView.addTrait(fooTrait)

return traitsView
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.
// Get the contract-level name and symbol values
let contractLevel = ExampleNFT.resolveContractView(
resourceType: nil,
viewType: Type<MetadataViews.EVMBridgedMetadata>()
) as! MetadataViews.EVMBridgedMetadata?
?? panic("Could not resolve contract-level EVMBridgedMetadata")
// Compose the token-level URI based on a base URI and the token ID, pointing to a JSON file. This
// would be a file you've uploaded and are hosting somewhere - in this case HTTP, but this could be
// IPFS, S3, a data URL containing the JSON directly, etc.
let baseURI = "https://example-nft.onflow.org/token-metadata/"
let uriValue = self.id.toString().concat(".json")

return MetadataViews.EVMBridgedMetadata(
name: contractLevel.name,
symbol: contractLevel.symbol,
uri: MetadataViews.URI(
baseURI: baseURI, // defining baseURI results in a concatenation of baseURI and value
value: self.id.toString().concat(".json")
)
)

}
return nil
}
Expand Down Expand Up @@ -154,7 +180,7 @@ access(all) contract ExampleNFT: NonFungibleToken {
}

/// withdraw removes an NFT from the collection and moves it to the caller
access(NonFungibleToken.Withdraw | NonFungibleToken.Owner) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
let token <- self.ownedNFTs.remove(key: withdrawID)
?? panic("Could not withdraw an NFT with the provided ID from the collection")

Expand All @@ -176,7 +202,7 @@ access(all) contract ExampleNFT: NonFungibleToken {
// Do not add to your contract unless you have a specific
// reason to want to emit the NFTUpdated event somewhere
// in your contract
let authTokenRef = (&self.ownedNFTs[id] as auth(NonFungibleToken.Owner) &{NonFungibleToken.NFT}?)!
let authTokenRef = (&self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}?)!
//authTokenRef.updateTransferDate(date: getCurrentBlock().timestamp)
ExampleNFT.emitNFTUpdated(authTokenRef)
}
Expand Down Expand Up @@ -225,7 +251,8 @@ access(all) contract ExampleNFT: NonFungibleToken {
access(all) view fun getContractViews(resourceType: Type?): [Type] {
return [
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>()
Type<MetadataViews.NFTCollectionDisplay>(),
Type<MetadataViews.EVMBridgedMetadata>()
]
}

Expand Down Expand Up @@ -264,6 +291,20 @@ access(all) contract ExampleNFT: NonFungibleToken {
"twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
}
)
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.
// Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
// but it could be IPFS, S3, a data URL containing the JSON directly, etc.
return MetadataViews.EVMBridgedMetadata(
name: "ExampleNFT",
symbol: "XMPL",
uri: MetadataViews.URI(
baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
value: "https://example-nft.onflow.org/contract-metadata.json"
)
)
}
return nil
}
Expand Down
77 changes: 75 additions & 2 deletions contracts/utility/MetadataViews.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,32 @@ access(all) contract MetadataViews {
}
}

/// View to represent a file with an correspoiding mediaType.
///
/// A struct to represent a generic URI. May be used to represent the URI of
/// the NFT where the type of URI is not able to be determined (i.e. HTTP,
/// IPFS, etc.)
///
access(all) struct URI: File {
/// The base URI prefix, if any. Not needed for all URIs, but helpful
/// for some use cases For example, updating a whole NFT collection's
/// image host easily
///
access(all) let baseURI: String?
/// The URI string value
/// NOTE: this is set on init as a concatenation of the baseURI and the
/// value if baseURI != nil
///
access(self) let value: String

access(all) view fun uri(): String {
return self.value
}

init(baseURI: String?, value: String) {
self.baseURI = baseURI
self.value = baseURI != nil ? baseURI!.concat(value) : value
}
}

access(all) struct Media {

/// File for the media
Expand Down Expand Up @@ -703,4 +727,53 @@ access(all) contract MetadataViews {
}
return nil
}
/// This view may be used by Cadence-native projects to define their
/// contract- and token-level metadata according to EVM-compatible formats.
/// Several ERC standards (e.g. ERC20, ERC721, etc.) expose name and symbol
/// values to define assets as well as contract- & token-level metadata view
/// `tokenURI(uint256)` and `contractURI()` methods. This view enables
/// Cadence projects to define in their own contracts how they would like
/// their metadata to be defined when bridged to EVM.
///
access(all) struct EVMBridgedMetadata {

/// The name of the asset
///
access(all) let name: String

/// The symbol of the asset
///
access(all) let symbol: String

/// The URI of the asset - this can either be contract-level or
/// token-level URI depending on where the metadata is resolved. It
/// is recommended to reference EVM metadata standards for how to best
/// prepare your view's formatted value.
///
/// For example, while you may choose to take advantage of onchain
/// metadata, as is the case for most Cadence NFTs, you may also choose
/// to represent your asset's metadata in IPFS and assign this value as
/// an IPFSFile struct pointing to that IPFS file. Alternatively, you
/// may serialize your NFT's metadata and assign it as a JSON string
/// data URL representating the NFT's onchain metadata at the time this
/// view is resolved.
///
access(all) let uri: {File}

init(name: String, symbol: String, uri: {File}) {
self.name = name
self.symbol = symbol
self.uri = uri
}
}

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

}
20 changes: 14 additions & 6 deletions contracts/utility/NonFungibleToken.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ The interface that all Non-Fungible Token contracts should conform to.
If a user wants to deploy a new NFT contract, their contract should implement
The types defined here
/// Contributors (please add to this list if you contribute!):
/// - Joshua Hannan - https://github.com/joshuahannan
/// - Bastian Müller - https://twitter.com/turbolent
/// - Dete Shirley - https://twitter.com/dete73
/// - Bjarte Karlsen - https://twitter.com/0xBjartek
/// - Austin Kline - https://twitter.com/austin_flowty
/// - Giovanni Sanchez - https://twitter.com/gio_incognito
/// - Deniz Edincik - https://twitter.com/bluesign
///
/// Repo reference: https://github.com/onflow/flow-nft
## `NFT` resource interface
The core resource type that represents an NFT in the smart contract.
Expand Down Expand Up @@ -49,9 +60,6 @@ access(all) contract interface NonFungibleToken: ViewResolver {
/// An entitlement for allowing updates and update events for an NFT
access(all) entitlement Update

/// entitlement for owner that grants Withdraw and Update
access(all) entitlement Owner

/// Event that contracts should emit when the metadata of an NFT is updated
/// It can only be emitted by calling the `emitNFTUpdated` function
/// with an `Updatable` entitled reference to the NFT that was updated
Expand All @@ -63,7 +71,7 @@ access(all) contract interface NonFungibleToken: ViewResolver {
/// and query the updated metadata from the owners' collections.
///
access(all) event Updated(type: String, id: UInt64, uuid: UInt64, owner: Address?)
access(all) view fun emitNFTUpdated(_ nftRef: auth(Update | Owner) &{NonFungibleToken.NFT})
access(all) view fun emitNFTUpdated(_ nftRef: auth(Update) &{NonFungibleToken.NFT})
{
emit Updated(type: nftRef.getType().identifier, id: nftRef.id, uuid: nftRef.uuid, owner: nftRef.owner?.address)
}
Expand Down Expand Up @@ -108,7 +116,7 @@ access(all) contract interface NonFungibleToken: ViewResolver {

/// Gets all the NFTs that this NFT directly owns
/// @return A dictionary of all subNFTS keyed by type
access(all) view fun getAvailableSubNFTS(): {Type: UInt64} {
access(all) view fun getAvailableSubNFTS(): {Type: [UInt64]} {
return {}
}

Expand Down Expand Up @@ -139,7 +147,7 @@ access(all) contract interface NonFungibleToken: ViewResolver {
/// withdraw removes an NFT from the collection and moves it to the caller
/// It does not specify whether the ID is UUID or not
/// @param withdrawID: The id of the NFT to withdraw from the collection
access(Withdraw | Owner) fun withdraw(withdrawID: UInt64): @{NFT} {
access(Withdraw) fun withdraw(withdrawID: UInt64): @{NFT} {
post {
result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID"
emit Withdrawn(type: result.getType().identifier, id: result.id, uuid: result.uuid, from: self.owner?.address, providerUUID: self.uuid)
Expand Down
33 changes: 0 additions & 33 deletions flow.mainnet.json

This file was deleted.

35 changes: 0 additions & 35 deletions flow.previewnet.json

This file was deleted.

Loading

0 comments on commit aa171af

Please sign in to comment.