この記事は筆者のソロ Advent Calendar 2022 22日目の記事です。
引き続きflowブロックチェーン上にスマートコントラクトを実装するためのCadenceという言語について公式ドキュメントのチュートリアルをやってみたのでその備忘録です。
今回はいよいよトークンを扱った処理のチュートリアルをやっていきたいと思います。最初はNon-Fungible Token(NFT)のチュートリアルになります!
NFT特化flowブロックチェーンに入門するためにcadence言語を学ぶ[Hello World編]
NFT特化flowブロックチェーンに入門するためにcadence言語を学ぶ[リソース編]
NFT特化flowブロックチェーンに入門するためにcadence言語を学ぶ[capability リンクの参照とスクリプト]
NFT特化flowブロックチェーンに入門するためにcadence言語を学ぶ[Non Fungible Tokens(NFT)編1] <- 今ここ
NFT特化flowブロックチェーンに入門するためにcadence言語を学ぶ[Non Fungible Tokens(NFT)編2]
NFT特化flowブロックチェーンに入門するためにcadence言語を学ぶ[Fungible Tokens(FT)編]
チュートリアルplaygroundはこちら
CadenceにおけるNFT
NFTの詳しい説明はここでは省略しますが、Cadenceでは、他のスマートコントラクト言語とは異なり、アカウント内のストレージにリソースオブジェクトとしてNFTを保存します。このため、NFTはCadenceの型システムによって、単一所有者となることや複製不可であること、偶発的な損失などを防ぐことができ、NFTをアカウント所持者が安全にデジタル資産として所持しているということを認識することができます。
NFTをアカウントに追加する
チュートリアルplaygroundの0x01
アカウントのページを開くと以下のようなNFTコントラクトがあるので中身を確認していきましょう。
pub contract BasicNFT {
// Declare the NFT resource type
pub resource NFT {
// The unique ID that differentiates each NFT
pub let id: UInt64
// String mapping to hold metadata
pub var metadata: {String: String}
// Initialize both fields in the init function
init(initID: UInt64) {
self.id = initID
self.metadata = {}
}
}
// Function to create a new NFT
pub fun createNFT(id: UInt64): @NFT {
return <-create NFT(initID: id)
}
// Create a single new NFT and save it to account storage
init() {
self.account.save<@NFT>(<-create NFT(initID: 1), to: /storage/BasicNFTPath)
}
}
前回までに学んだ内容で理解することができますが、アカウント内にBasicNFT
という名前でコントラクトを定義し、その内部でNFT
という名前のリソースを宣言しています。リソースには整数値のid
とmapでmetadata
をフィールドとして持つようにし、initでフィールドの初期化をしています。
NFTリソースの作成にはcreateNFT
関数を定義していて、コントラクトの初期化処理で自信のアカウントストレージに作成したNFTを保存しています。
NFTが保存できているかを確認するために、このコントラクトをデプロイし、NFT Existsトランザクションを実行してみます。トランザクションの中身は以下のようになっています。
// NFT Exists
import BasicNFT from 0x01
// This transaction checks if an NFT exists in the storage of the given account
// by trying to borrow from it. If the borrow succeeds (returns a non-nil value), the token exists!
transaction {
prepare(acct: AuthAccount) {
if acct.borrow<&BasicNFT.NFT>(from: /storage/BasicNFTPath) != nil {
log("The token exists!")
} else {
log("No token found!")
}
}
}
ここでは、前回の記事で紹介したcapabilityは使用せず、アカウントストレージから直接NFTリソースへの参照を借用し、存在していれば成功のログを出すような処理をしています。こちらを実行してみると以下のように、ちゃんとNFTが存在していることがわかります。
NFT Exists "The token exists!"
NFTの移動
別のアカウントにNFTを移動させる方法についてですが、チュートリアルに「ここは学習のために自分で考えてみてね」とあるので自力でやってみる。まず、playgroundのBasic Transferを開くと以下のようになっている。
import BasicNFT from 0x01
/// Basic transaction for two accounts to authorize
/// to transfer an NFT
transaction {
prepare(signer1: AuthAccount, signer2: AuthAccount) {
// Fill in code here to load the NFT from signer1
// and save it into signer2's storage
}
}
prepareブロックの中を埋めてみる
import BasicNFT from 0x01
/// Basic transaction for two accounts to authorize
/// to transfer an NFT
transaction {
prepare(signer1: AuthAccount, signer2: AuthAccount) {
// Fill in code here to load the NFT from signer1
// and save it into signer2's storage
+ let nftResource <- signer1.load<@BasicNFT.NFT>(from: /storage/BasicNFTPath)
+ signer2.save(<- nftResource, to: /storage/BasicNFTPath)
+ log(signer2.borrow<&BasicNFT.NFT>(from: /storage/BasicNFTPath) != nil)
}
}
signer1からNFTを読み込んで、signer2に保存して、signer2のストレージを参照してみてログに出してみる。保存されていればtrueが出力されるはず。実行してみる
Basic Transfer Error failed to force-cast value: expected type `BasicNFT.NFT`, got `BasicNFT.NFT?`
エラーになった。読み込んだリソースがオプショナルなのを考慮していないためエラーになっているっぽい。以下のように修正
let nftResource <- signer1.load<@BasicNFT.NFT>(from: /storage/BasicNFTPath)
?? panic("Could not load NFT")
実行してみる
Basic Transfer true
無事、NFTを移動させることができた
おまけ Solidityとの比較
チュートリアルで作成したBasic.cdcの内容をSolidityで書いてみました。Solidityはまだ学習中なので誤った点があればコメントください。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract BasicNFT {
uint private tokenId;
mapping(address => uint[]) private ownedNFTMapping;
constructor() {
ownedNFTMapping[msg.sender].push(1);
tokenId = 1;
}
function createNFT() public {
tokenId++;
ownedNFTMapping[msg.sender].push(tokenId);
}
function getId() public view returns (uint[] memory) {
return ownedNFTMapping[msg.sender];
}
}
なんとなく似たようなことをSolidityでやろうとするとこうなる。Cadenceとの大きな違いが
mapping(address => uint[]) private ownedNFTMapping;
この部分でSolidityでアカウントがNFTを所持するようにするにはmapでアカウントアドレスに対してNFT情報を紐付けコントラクトのフィールドで記録するような実装をする必要があり中央台帳的な管理になります。
CadenceではアカウントのストレージにNFTリソースを保存することができるので現実世界の人がものを所有するということをコードで表現できてるような気がします。
まとめ
今回は以下のことについて紹介しました。
- NFTをアカウントストレージ内に保存する方法について
- アカウントのNFTを別のアカウントに移動する方法について
感覚的な話ですがアカウントのストレージ内にNFTリソースが保存されているのが非常にわかりやすいというか所持してるんだなという気がしました。まだ、実際に出回っているNFTをどう実装するのかについての理解が足りないので引き続き次回もNFTについて学んでいきたいと思います!今回は以上です!