Cadence言語のリソースという概念の扱い方を整理。
目次
コントラクトを定義する
リソースを使うためには基本的に
- リソースを定義する
- リソースを生成する関数を定義する
- デプロイ時の初期化処理を実装する
という流れで実装する
コードのイメージは以下:
- 解説は後述
pub contract BasicNFT { // リソースを定義する pub resource NFT { pub let id: UInt64 pub var metadata: {String: String} init(initID: UInt64) { self.id = initID self.metadata = {} } } // リソースを生成する関数を定義する pub fun createNFT(id: UInt64): @NFT { return <-create NFT(initID: id) } // デプロイ時の初期化処理を実装する init() { self.account.save<@NFT>(<-create NFT(initID: 1), to: /storage/BasicNFTPath) } }
リソースを定義する
// リソースを定義する pub resource NFT { // 必要なフィールドを定義する pub let id: UInt64 pub var metadata: {String: String} // 定義したフィールドを初期化する init(initID: UInt64) { self.id = initID self.metadata = {} } }
- リソースが生成されるごとに実行される処理となる
- 必ず全てのフィールドを初期化しなければならない
- 試しに Playgound で
self.metadata
を削除するとエラーになる
- 試しに Playgound で
リソースを生成する関数を定義する
// リソースを生成する関数を定義する // `@`マークはそれがリソースオブジェクトであることを示している // リソースは `create`と`<-`を使って生成できる pub fun createNFT(id: UInt64): @NFT { return <-create NFT(initID: id) }
この関数はトランザクションで NFT を生成するために利用される
<-
は Move operater と呼ばれ、値の「コピー」ではなく「移動」であることを表現している- 以下のタイミングで利用する
- リソースが初期化(インスタンス化)されるとき
- リソースが別の変数に設定されるとき
- リソースが関数の引数として渡されるとき
- リソースが関数から返されるとき
- 以下のタイミングで利用する
例えば以下のコードの場合、
second_resource
にリソースインスタンスが格納されたら、first_resource
には何も入っていない状態になるfirst_resource
は nil になるわけではなく、そもそもfirst_resource
は存在しないことになり、アクセスできなくなる
var fist_resource: @HogeResource <- create AnyResource() var second_resource <- first_resource
デプロイ時の初期化処理を実装する
// デプロイ時の初期化処理を実装する // デプロイしたアカウントのストレージに1つだけ NFT を保存している init() { self.account.save<@NFT>(<-create NFT(initID: 1), to: /storage/BasicNFTPath) }
- コントラクトの init()関数に処理を記載すると、デプロイ時に一度だけ実行させることができる
- 本例では
NFT
リソースを/storage/BasicNFTPath
に ID:1 をつけて格納している - コントラクトはアカウントのストレージへの読み書きアクセスを
self.account
オブジェクトを使って実装できる- 本オブジェクトは
AuthAccount
オブジェクトと呼ばれ、アカウントのストレージを操作するための関数にアクセスできる権限を持っている
- 本オブジェクトは
デプロイする
上記のコントラクトを 0x01 でデプロイすると以下の図の状態になる。
トランザクションでリソースを扱う
新たにリソースを生成する
- デプロイしたコントラクトを使ってリソースの生成や移動をするには、トランザクションコードを実装する
- 以下はリソースを新たに生成し、ストレージに保存する例
- リソースは署名したアカウントのストレージに保存される
- 本例では特に制限していないため 0x01 以外のアカウントでも自由にリソースを生成し保存できる(権限制御は後述)
import BasicNFT from 0x01 transaction { prepare(acct: AuthAccount){ // 0x01 がデプロイしたコントラクト `BasicNFT` の`createNFT()`を使ってリソースを生成 let newNFT <- BasicNFT.createNFT(id: 1) // このトランザクションを署名した AuthAccount の save メソッドを使ってストレージに生成した NFT を保存 acct.save<@BasicNFT.NFT>(<-newNFT, to: /storage/BasicNFTPath2) } execute { log("Saved NFT") } }
prepare について
リソースの移動について
- ここでは AuthAccount のメソッドを使ってリソースを移動している
- 使い方は
AuthAccount.save<T>(_ value: T, to: StoragePath)
- 詳細は、account storage APIに記載されている
- T はリソースオブジェクトの型を設定しないといけないため、ここでは newNFT の型、つまり
@BasicNFT.NFT
になる - 指定した移動先にすでにデータが存在していたり、newNFT にリソースが存在しない場合は、プログラムが止まるようになっている
- そのためパスコンフリクトが置きないように、ユニークで明確なパス名を指定する必要がある
0x02 をトランザクションを実行すると以下の図になる
アカウントに保存したリソースを取り出す
- 最後に、保存したリソースを取り出す方法を確認する
- ここでは 0x01 が署名して実行したとする
import BasicNFT from 0x01 transaction { prepare(acct: AuthAccount){ // ストレージからリソースを読み込み、NFTResourceに移動する // この時、オプショナル型で格納される let NFTResource <- acct.load<@BasicNFT.NFT>(from: /storage/BasicNFTPath) log(NFTResource?.id) // 値がloadできなかった場合は nil になる log(NFTResource?.metadata) // 値がloadできなかった場合は nil になる // アカウントストレージにリソースを再度saveしている // この時、ストレージにはオプショナル型は入れられないため!でforce-unwrapしている // nilの場合はforce-unwrapできないためpanicになりrevertする acct.save<@BasicNFT.NFT>(<-NFTResource!, to: /storage/BasicNFTPath) } }
ストレージからリソースを取得した時には、その値は optional 型で return される
イメージ図