Skip to content

Commit

Permalink
don't use interface in NFT tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuahannan committed Sep 17, 2024
1 parent 8e494b4 commit fa1714a
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 73 deletions.
5 changes: 0 additions & 5 deletions docs/tutorial/01-first-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ deploy them to a local Flow emulated blockchain, and submit transactions.
The Flow Playground should work with any standard web browser.
However, we recommend using Google Chrome, as it has been primarily tested and optimized for this browser.

The [Flow Crescendo (Cadence 1.0) upgrade](https://flow.com/upgrade/crescendo)
introduces new and powerful features to Cadence. Currently, the playground uses an older version
of Cadence, but you can interact with a Cadence 1.0 playground by going to
https://v1.play.flow.com/.

## Getting to know the Playground

The Playground contains everything you need to get familiar
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/02-hello-world.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ archived: false
draft: false
title: 2. Hello World
description: A smart contract tutorial for Cadence.
date: 2024-09-05
date: 2024-09-17
meta:
keywords:
- tutorial
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/03-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ archived: false
draft: false
title: 3. Resource Contract Tutorial
description: An introduction to resources, capabilities, and account storage in Cadence
date: 2024-09-05
date: 2024-09-17
meta:
keywords:
- tutorial
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/04-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ archived: false
draft: false
title: 4. Capability Tutorial
description: An introduction to capabilities and how they interact with resources in Cadence
date: 2024-09-05
date: 2024-09-17
meta:
keywords:
- tutorial
Expand Down
96 changes: 31 additions & 65 deletions docs/tutorial/05-non-fungible-tokens-2.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,6 @@ a full implementation for **Non-Fungible Tokens (NFTs)**.
The tutorial will ask you to take various actions to interact with this code.
</Callout>

<Callout type="info">
The playground code that is linked uses Cadence 0.42, but the examples
use Cadence 1.0 to show how each contract, transaction and script
is implemented in Cadence 1.0.
You can access a Cadence 1.0-compatible playground by going to https://v1.play.flow.com/.
The project link will still work with the current version of the playground,
but when the playground is updated to Cadence 1.0, the link will be replaced with a 1.0-compatible version.
</Callout>

<Callout type="info">
Instructions that require you to take action are always included in a callout box like this one.
These highlighted actions are all that you need to do to get your code running,
Expand Down Expand Up @@ -131,21 +122,21 @@ concepts this contract introduces.

<Callout type="info">

Open Account `0x06` to see `ExampleNFT.cdc`.<br/>
Open the `ExampleNFT` contract.<br/>
Deploy the contract by clicking the Deploy button in the bottom right of the editor.<br/>
`ExampleNFT.cdc` should contain the code below.
It contains what was already in `BasicNFT.cdc` plus additional resource declarations in the contract body.

</Callout>

```cadence ExampleNFT.cdc
// ExampleNFT.cdc
//
// This is a complete version of the ExampleNFT contract
// that includes withdraw and deposit functionalities, as well as a
// collection resource that can be used to bundle NFTs together.
//
// Learn more about non-fungible tokens in this tutorial: https://developers.flow.com/cadence/tutorial/non-fungible-tokens-1
/// ExampleNFT.cdc
///
/// This is a complete version of the ExampleNFT contract
/// that includes withdraw and deposit functionalities, as well as a
/// collection resource that can be used to bundle NFTs together.
///
/// Learn more about non-fungible tokens in this tutorial: https://developers.flow.com/cadence/tutorial/non-fungible-tokens-1
access(all) contract ExampleNFT {
Expand All @@ -156,7 +147,7 @@ access(all) contract ExampleNFT {
access(all) let CollectionPublicPath: PublicPath
access(all) let MinterStoragePath: StoragePath
// Tracks the unique IDs of the NFT
// Tracks the unique IDs of the NFTs
access(all) var idCount: UInt64
// Declare the NFT resource type
Expand All @@ -170,24 +161,11 @@ access(all) contract ExampleNFT {
}
}
// We define this interface purely as a way to allow users
// to create public, restricted references to their NFT Collection.
// They would use this to publicly expose only the deposit, getIDs,
// and idExists fields in their Collection
access(all) resource interface NFTReceiver {
access(all) fun deposit(token: @NFT)
access(all) fun getIDs(): [UInt64]
access(all) fun idExists(id: UInt64): Bool
}
access(all) entitlement Withdraw
// The definition of the Collection resource that
// holds the NFTs that a user owns
access(all) resource Collection: NFTReceiver {
access(all) resource Collection {
// dictionary of NFT conforming tokens
// NFT is a resource type with an `UInt64` ID field
access(all) var ownedNFTs: @{UInt64: NFT}
Expand All @@ -204,7 +182,10 @@ access(all) contract ExampleNFT {
access(Withdraw) fun withdraw(withdrawID: UInt64): @NFT {
// If the NFT isn't found, the transaction panics and reverts
let token <- self.ownedNFTs.remove(key: withdrawID)
?? panic("Could not withdraw an ExampleNFT.NFT with the specified ID")
?? panic("Could not withdraw an ExampleNFT.NFT with id="
.concat(withdrawID.toString())
.concat("Verify that the collection owns the NFT ")
.concat("with the specified ID first before withdrawing it."))
return <-token
}
Expand Down Expand Up @@ -263,7 +244,7 @@ access(all) contract ExampleNFT {
self.account.storage.save(<-self.createEmptyCollection(), to: self.CollectionStoragePath)
// publish a capability to the Collection in storage
let cap = self.account.capabilities.storage.issue<&{NFTReceiver}>(self.CollectionStoragePath)
let cap = self.account.capabilities.storage.issue<&Collection>(self.CollectionStoragePath)
self.account.capabilities.publish(cap, at: self.CollectionPublicPath)
}
}
Expand Down Expand Up @@ -373,23 +354,9 @@ it is by default not available for other users to access
because it requires access to the authorized account object (`auth(Storage) &Account`)
which is only accessible by a transaction that the owner authorizes and signs.

To give external accounts access to the `deposit` function,
the `getIDs` function, and the `idExists` function, the owner creates an interface that only includes those fields:

```cadence
access(all)
resource interface NFTReceiver {
access(all) fun deposit(token: @NFT)
access(all) fun getIDs(): [UInt64]
access(all) fun idExists(id: UInt64): Bool
}
```
To give external accounts access to the `access(all)` fields and functions,
the owner creates a link to the object in storage.

Then, using that interface, they would create a link to the object in storage,
specifying that the link only contains the functions in the `NFTReceiver` interface.
This link creates a capability. From there, the owner can then do whatever they want with that capability:
they could pass it as a parameter to a function for one-time-use,
or they could put in the `/public/` domain of their account so that anyone can access it.
Expand All @@ -399,11 +366,11 @@ in the `ExampleNFT.cdc` contract initializer.

```cadence
// publish a capability to the Collection in storage
let cap = self.account.capabilities.storage.issue<&{NFTReceiver}>(self.CollectionStoragePath)
let cap = self.account.capabilities.storage.issue<&Collection>(self.CollectionStoragePath)
self.account.capabilities.publish(cap, at: self.CollectionPublicPath)
```

The `issue` function specifies that the capability is typed as `&AnyResource{NFTReceiver}` to only expose those fields and functions.
The `issue` function specifies that the capability is typed as `&Collection`.
Then the link is published to `/public/` which is accessible by anyone.
The link targets the `/storage/NFTCollection` (through the `self.CollectionStoragePath` contract field) that we created earlier.

Expand Down Expand Up @@ -435,7 +402,7 @@ access(all) fun main(): [UInt64] {
// Find the public Receiver capability for their Collection and borrow it
let receiverRef = nftOwner.capabilities
.borrow<&{ExampleNFT.NFTReceiver}>(ExampleNFT.CollectionPublicPath)
.borrow<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
?? panic("Could not borrow receiver reference to the ExampleNFT.Collection")
// Log the NFTs that they own as an array of IDs
Expand Down Expand Up @@ -466,11 +433,10 @@ If you run into issues, make sure that you deployed the contract in account `0x0
We do not want everyone in the network to be able to call our `withdraw` function though.
In Cadence, any reference can be freely up-casted or down-casted to any subtype or supertype
that the reference conforms to. This means that if I had a reference of the type
`&{ExampleNFT.NFTReceiver}`, I could cast it to `&ExampleNFT.Collection`, which exposes
all the `access(all)` functions on the `Collection`.
`&ExampleNFT.Collection`, this would expose all the `access(all)` functions on the `Collection`.

This is a powerful feature that is very useful, but developers need to understand that
this means that if there is any privileged functionality on an resource that has a
this means that if there is any privileged functionality on a resource that has a
public capability, then this functionality cannot be `access(all)`.
It needs to use [Entitlements](../language/access-control#entitlements).

Expand Down Expand Up @@ -520,7 +486,7 @@ using the `auth` keyword:
// publish an entitled capability to the Collection in storage
// This capability is issued with the `auth(ExampleNFT.Withdraw)` entitlement
// This gives access to the withdraw function
let cap = self.account.capabilities.storage.issue<auth(ExampleNFT.Withdraw) &{NFTReceiver}>(self.CollectionStoragePath)
let cap = self.account.capabilities.storage.issue<auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>(self.CollectionStoragePath)
self.account.capabilities.publish(cap, at: self.CollectionPublicPath)
```

Expand Down Expand Up @@ -573,12 +539,12 @@ import ExampleNFT from 0x06
transaction {
// The reference to the collection that will be receiving the NFT
let receiverRef: &{ExampleNFT.NFTReceiver}
let receiverRef: &ExampleNFT.Collection
prepare(acct: AuthAccount) {
// Get the owner's collection capability and borrow a reference
self.receiverRef = acct.capabilities
.borrow<&{ExampleNFT.NFTReceiver}>(ExampleNFT.CollectionPublicPath)
.borrow<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
?? panic("Could not borrow receiver reference to ExampleNFT.Collection")
}
Expand Down Expand Up @@ -610,7 +576,7 @@ access(all) fun main(): [UInt64] {
let nftOwner = getAccount(0x06)
// Find the public Receiver capability for their Collection
let capability = nftOwner.capabilities.get<&{ExampleNFT.NFTReceiver}>(ExampleNFT.CollectionPublicPath)
let capability = nftOwner.capabilities.get<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
// borrow a reference from the capability
let receiverRef = capability.borrow()
Expand Down Expand Up @@ -658,7 +624,7 @@ transaction {
log("Collection created for account 2")
// create a public capability for the Collection
let cap = acct.capabilities.storage.issue<&{ExampleNFT.NFTReceiver}>(ExampleNFT.CollectionStoragePath)
let cap = acct.capabilities.storage.issue<&ExampleNFT.Collection>(ExampleNFT.CollectionStoragePath)
acct.capabilities.publish(cap, at: ExampleNFT.CollectionPublicPath)
log("Capability created")
Expand Down Expand Up @@ -691,7 +657,7 @@ transaction {
// Borrow a reference from the stored collection
let collectionRef = acct.storage
.borrow<&ExampleNFT.Collection>(from: ExampleNFT.CollectionStoragePath)
.borrow<auth(ExampleNFT.Withdraw) &ExampleNFT.Collection>(from: ExampleNFT.CollectionStoragePath)
?? panic("Could not borrow a reference to the owner's collection")
// Call the withdraw function on the sender's Collection
Expand All @@ -706,7 +672,7 @@ transaction {
// Get the Collection reference for the receiver
// getting the public capability and borrowing a reference from it
let receiverRef = recipient.capabilities
.borrow<&{ExampleNFT.NFTReceiver}>(ExampleNFT.CollectionPublicPath)
.borrow<&ExampleNFT.Collection>(ExampleNFT.CollectionPublicPath)
?? panic("Could not borrow receiver reference")
// Deposit the NFT in the receivers collection
Expand Down Expand Up @@ -742,9 +708,9 @@ access(all) fun main() {
let acct2Capability = account2.capabilities.get(ExampleNFT.CollectionPublicPath)
// borrow references from the capabilities
let receiver1Ref = acct1Capability.borrow<&{ExampleNFT.NFTReceiver}>()
let receiver1Ref = acct1Capability.borrow<&ExampleNFT.Collection>()
?? panic("Could not borrow account 1 receiver reference")
let receiver2Ref = acct2Capability.borrow<&{ExampleNFT.NFTReceiver}>()
let receiver2Ref = acct2Capability.borrow<&ExampleNFT.Collection>()
?? panic("Could not borrow account 2 receiver reference")
// Print both collections as arrays of IDs
Expand Down

0 comments on commit fa1714a

Please sign in to comment.