この記事は筆者のソロ Advent Calendar 2022 20日目の記事です。
前回に引き続きflowブロックチェーン上にスマートコントラクトを実装するためのCadenceという言語について公式ドキュメントのチュートリアルをやってみたのでその備忘録です。
Cadenceはリソース指向言語であるという風に聞いたことがありますが今回はリソースやアカウントのリソースについて学んでいきたいと思います。
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)編]
HelloWorld
とりあえずHelloWorldしてみます。
コントラクトのデプロイ
公式ドキュメント記載のplaygroundを使用します。
上記playgroundを開くとアカウント0x01にコントラクト実装が用意されているのでDeployボタンをクリックしデプロイします。
リソースをアカウントストレージに保存する
playgroundのTransaction Templatesの項目にCreate Hello
というトランザクションが用意されているためこれを実行します。成功すると署名アカウント内のストレージにHelloAssetが作成され保存されます。
リソースを読み込みHello Worldする
playgroundのTransaction Templatesの項目にLoad Hello
というトランザクションが用意されているためこれを実行します。成功すると画面下部のコンソールに以下のようにHello Worldが出力されます。
Load Hello > "Hello, World!"
ソースコードの確認
HelloWorldコントラクトの実装
// HelloWorldResource.cdc
//
// This is a variation of the HelloWorld contract that introduces the concept of
// resources, a new form of linear type that is unique to Cadence. Resources can be
// used to create a secure model of digital ownership.
//
// Learn more about resources in this tutorial: https://developers.flow.com/cadence/tutorial/03-resources
pub contract HelloWorld {
// Declare a resource that only includes one function.
pub resource HelloAsset {
// A transaction can call this function to get the "Hello, World!"
// message from the resource.
pub fun hello(): String {
return "Hello, World!"
}
}
// We're going to use the built-in create function to create a new instance
// of the HelloAsset resource
pub fun createHelloAsset(): @HelloAsset {
return <-create HelloAsset()
}
init() {
log("Hello Asset")
}
}
前回同様HelloWorldコントラクトの実装ですがコントラクトの中にresourceというものを宣言しており、このresource内にhello関数が定義されています。また、コントラクトにはこのresourceを作成するcreateHelloAssetが定義されています。
resourceの初期化と移動演算子
まず、コントラクト内に定義されているcreateHelloAsset関数を見ていきます。<-
という演算子はmove operator(移動演算子)と呼ばれ、リソースを変数に移動させるために使用します。resourceには=
のような代入演算子を使用することができないためリソースを初期化するには移動演算子を使用する必要があります。また、戻り値のHelloAssetの先頭に@
がついていますがこれはリソースを表すための記号となっており、リソースの型には必ず付ける必要があります。
create
という修飾子はリソースを初期化するために使用され、リソースを使用する前に作成する必要がある。
resourceの宣言
コントラクトの宣言と同じ感じ時でresourceブロックを宣言することでresourceを定義します。今回の例ではHelloAssetというresourceを宣言しています。resourceの内部にはHello Worldを返すhello関数のみ定義してあります。
resourceとstructやclassの主な違いは、リソースのアクセススコープです。リソースの各インスタンスは正確に一つの場所にのみ存在することができ、コピーすることはできません。そのため、リソースにアクセスする際には、ある場所から別の場所に明示的に移動させる必要がある。また、関数の実行終了時にリソースがスコープ外に出ることはできません。明示的にどこかに保存するか、破棄する必要があります。
CreateHelloトランザクションの実装
import HelloWorld from 0x01
transaction {
// No need to do anything in prepare because we are not working with
// account storage.
prepare(acct: AuthAccount) {
let newHello <- HelloWorld.createHelloAsset()
acct.save<@HelloWorld.HelloAsset>(<-newHello, to: /storage/HelloAssetTutorial)
}
// In execute, we log a string to confirm that the transaction executed successfully.
execute {
log("Saved Hello Resource to account.")
}
}
前回詳しく説明しなかったtransactionブロック内のprepareブロック内でインポートしたHelloWorldコントラクトのcreateHelloAsset関数を実行し作成したリソースを変数に移動させています。
prepareブロックはAccount型の引数をとります。AccountはPublicAccount
とAuthAccount
の2つがあり、全てのアカウントはこれらを使用しアクセスすることができます。AuthAccount
は署名済みの認可されたアカウントへのアクセスであり、そのアカウントが持つストレージや公開鍵、コードへの完全なアクセスを提供します。
作成したリソースをアカウントストレージに保存するのにアカウントストレージAPIを使用することができ、保存するにはsaveメソッドを使用します。saveメソッドの第一引数は保存するリソースであり、第二引数のtoパラメーターには保存するパスを指定します。
また、saveメソッドは型パラメーターに保存するリソースの型を指定する。
LoadHelloトランザクションの実装
import HelloWorld from 0x01
transaction {
prepare(acct: AuthAccount) {
// load the resource from storage, specifying the type to load it as
// and the path where it is stored
let helloResource <- acct.load<@HelloWorld.HelloAsset>(from: /storage/HelloAssetTutorial)
// We use optional chaining (?) because the value in storage
// may or may not exist, and thus is considered optional.
log(helloResource?.hello())
// Put the resource back in storage at the same spot
// We use the force-unwrap operator `!` to get the value
// out of the optional. It aborts if the optional is nil
acct.save(<-helloResource!, to: /storage/HelloAssetTutorial)
}
}
上記のトランザクションでは保存したリソースを読み込み、読み込んだリソースで定義されたhello関数を呼び出しています。
リソースの読み込みにはアカウントAPIのloadメソッドを使用し、引数にfromパラメーターでパスの指定と型パラメーターでリソースの型を指定します。
次に読み込んだリソースのhelloメソッドを読んでいますが、helloResourceの最後に?
がついています。これは他の言語でもありますがオプショナル変数の呼び出しとなっており、loadメソッドで読み込んだリソースは存在しなければnilを返すためオプショナルな変数となります。そのためリソースの関数呼び出しには?
を最後につけて呼び出しています。
最後に再度saveメソッドを呼び出し、読み込んだリソースをアカウントストレージに返しています。この行がないとloss of resource
というコンパイルエラーになってしまうのでリソースをアカウントストレージから読み込んだ場合にはそれを返す処理が必要です。
saveメソッドの実行時に指定するリソースはオプショナルであるため!
を最後につけて呼び出す必要があります。仮にリソースがnilであればトランザクション全体が中止されます。
まとめ
今回は以下の内容について紹介いたしました。
- コントラクト内でリソースを定義する方法について
- 定義したリソースの作成と移動演算子の使用について
- アカウントAPIを使用したリソースの保存と読み込みについて
Cadence特有のリソースの扱いについて今回は紹介いたしました。次回も引き続き Cadenceのチュートリアルの内容のし紹介をしていきたいと思います。今回は以上です!