0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

3. Resource Contract Tutorial

Last updated at Posted at 2024-11-03

Overview

💡 TIP
このチュートリアルのスターターコードを Flow Playground で開きます。
https://play.flow.com/ddf0177e-81c8-4512-ac2e-28036b1a3f89
チュートリアルでは、このコードとやり取りするためにさまざまな操作を行うよう求められます。

ACTION
ユーザーにアクションを要求する指示は、常にこの吹き出しボックス内に表示されます。ハイライトされたアクションは、コードを実行するために必要なすべてですが、言語の設計を理解するには、残りの部分を読むことも必要です。

このチュートリアルは、前回のHello Worldチュートリアルを基にしています。このチュートリアルを開始する前に、次の内容を理解しておく必要があります。

このチュートリアルではアカウントについて理解を深めつつ、Resourceを紹介し、これによってアカウントとどうやりとりするのかを習得していきます。

リソースは、Cadenceの最も重要な機能の1つです。

Cadenceでは、リソースは他の言語における構造体やクラスに似た複合型の一つですが、いくつかの特別なルールを持っています。

以下に、リソースの宣言例を示します。

access(all) resource Money {

  access(all) let balance: Int

  init() {
    self.balance = 0
  }
}

ご覧下さい、リソースは通常のstruct定義みたいに見えます。その違いは動作内にあります。

Resourceは、アセット及びオブジェクトの直接所有(direct ownership)をモデル化したい場合に便利です。直接所有により、アクセスを可能にするパスワードや証明書の代わりにあなたの資産を表す実際のオブジェクトを所有できることを意味します。他のプログラミング言語の従来の構造体やクラスは、コピーが可能であるため、直接所有を表す理想的な方法にはなりません。コーディングエラーによって同じ資産の複数コピーが簡単に作成することが出来ると、資産の価値を持たせるための希少性を台無しにするからです。私たちは、家屋、自動車、数百万ドルの銀行口座、あるいは馬などの規模での損失や盗難を考慮しなければなりません。リソースはこの点で、資産を明示的に作成、破棄、移動させることでこの問題を解決させます。

このチュートリアルでは以下のことを行います。

  1. リソースを宣言したスマートコントラクトをデプロイします
  2. リソースをアカウントのストレージに保存します
  3. トランザクションを使用して作成したリソースに対しやりとりします

Implementing a Contract with Resources

リソースに対してやり取りするには、いくつかの重要な概念を学んでください。

まず、createキーワードと<-移動演算子を使ってどうやってリソースを作成していくかを見ていきましょう。
createキーワードを使用してリソースを初期化します。リソースはそれを定義したスマートコントラクトの中でのみ作成できます。

<-移動演算子は、リソースを変数に移動させるために使用します。リソースには代入演算子=を使用できないため、リソースを初期化したり、新しい変数に代入する時には、<-移動演算子を使用する必要があります。これは、リソースが文字通りある場所から別の場所に移動することを意味します。そのリソースを保持していた古い変数または場所は移動後、無効な値になります。これがCadenceがリソースを1場所にしか同時に存在させないことを保証する手段の1つです。

ACTION
ファイル名がHelloWorldResource.cdcであるアカウント0x06タブを開きます。
HelloWorldResource.cdcには、以下のコードが含まれているはずです。

access(all) contract HelloWorld {

    // Declare a resource that only includes one function.
    access(all) resource HelloAsset {

        // A transaction can call this function to get the "Hello, World!"
        // message from the resource.
        access(all) view 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
    access(all) fun createHelloAsset(): @HelloAsset {
        return <-create HelloAsset()
    }
}

ACTION
Deployボタンを使用して、このコードをアカウント0x06にデプロイします。

まず私達は、0x06アカウントに新しいHelloWorldコントラクトを宣言します。このHelloWorldコントラクトで私たちは以下をしています。

  1. HelloAssetリソースをパブリックスコープであるaccess(all)で宣言します。
  2. hello()リソース関数をHelloAssetの中に、パブリックスコープであるaccess(all)で宣言します。
  3. HelloAssetリソースをcreateするcreateHelloAsset()コントラクト関数を宣言します。
  4. createHelloAsset()関数はリソースを関数が返すように、移動演算子(<-)を使用します。

これは、スマートコントラクトで実行できることの一例です。Cadenceは、デプロイされたスマートコントラクト内で型の定義を宣言できます。(補足: Cadenceでは一つ一つのリソースが一つ一つの型になります。)型の定義は、特定のデータセットがどのように構成されているかを説明するものです。型の定義自体は、そのデータのコピーやインスタンスではありません。他のアカウントはこれらの定義をインポートし、それらの型のオブジェクトとやり取りすることができます。

私達が先ほどデプロイしたこのスマートコントラクトでは、HelloAssetリソースの定義とリソースを作成する関数の宣言をしています。

このスマートコントラクトについて、リソース部分から詳しく見ていきましょう。リソースとは、Cadenceがスマートコントラクト設計に導入した最も重要なものの1つです。

access(all)
resource HelloAsset {
    access(all)
    view fun hello(): String {
        return "Hello, World!"
    }
}

Resources

resourceと、structまたはclassの主な違いは、リソースのアクセス範囲です。

  • resourceの各インスタンスは、厳密に1つの場所にのみ存在することができ、コピーすることはできません。ここでいう場所とは、アカウントストレージ、関数内の一時変数、スマートコントラクト内のストレージ フィールドなどを指します。
  • resourceは、アクセスされると、明示的に1つの場所から別の場所に移動されなければなりません。
  • また、resourceは関数実行の終了時にスコープ外に出ることもできません。明示的にどこかに保存するか、明示的に破棄しなければなりません。

これらの特性により、コーディングのミスからリソースを誤って失うことが不可能になります。

resourceは、resourceが定義されたスコープ(補足: スマートコントラクト)内でのみ作成できます。

これにより、他のユーザーが定義したリソースオブジェクトを、誰でも任意の数だけ作成できることを防止できます。

The Move Operator (<-)

今回の例では、HelloAssetリソースを作成できる関数を宣言しました。

access(all)
fun createHelloAsset(): @HelloAsset {
    return <-create HelloAsset()
}

@シンボルは、それがスマートコントラクト内で定義したHelloAsset型のリソースであることを指し示しています。この関数では、HelloAsset型のリソースを作成しそれを返すために、移動演算子を使用しています。新しいリソースオブジェクトを作成するためには、createキーワードを使用します。

ここで、<-記号を使用しています。これは移動演算子です。<-移動演算子は、リソースにおける代入の代入演算子=を置き換えます。リソースの代入である、と明示する必要があるため、以下の処理を行う時には<-移動演算子を使用する必要があります。

  • リソースを定数または変数の初期値とする時
  • リソースが代入で別の変数に移動する時
  • リソースが引数として関数に移動する時
  • リソースが関数から返される時

リソースが移動すると、古い場所は無効となり、リソースオブジェクトは新しい場所のコンテキストに移動します。

例えば、変数first_resourceにリソースがあるとすると:

// Note the `@` symbol to specify that it is a resource
var first_resource: @AnyResource <- create AnyResource()

そして、それをsecond_resourceという新しい変数に代入したいとします。代入を行った後、first_resourceは無効となります。これは、リソースが新しい変数に移動したためです。

var second_resource <- first_resource
// first_resource is now invalid. Nothing can be done with it

リソースを通常の代入することは許可されていません。通常の代入は値をコピーするだけだからです。リソースは同時に1つの場所にしか存在できないため、移動は移動演算子<-を使用してコード内で明示して示される必要があります。

Create Hello Transaction

では次に、createHelloAsset()関数を実行し、HelloAssetリソースをアカウントのストレージに保存するトランザクション処理を行います。

ACTION
Create Helloという名前のトランザクションを開きます。
Create Helloには以下のコードを含めます。

/// create_hello.cdc
/// This transaction calls the createHelloAsset() function from the contract
/// to create a resource, then saves the resource
/// in the signer's account storage using the "save" method.
import HelloWorld from 0x06

transaction {

    /// `auth(SaveValue) &Account` signifies an account object
    /// that has the `SaveValue` authorization entitlement, which means
    /// that this transaction can't do anything with the &Account object
    /// besides saving values to storage.
    /// You will learn more about entitlements later
	prepare(acct: auth(SaveValue) &Account) {
        // Here we create a resource and move it to the variable newHello,
        // then we save it in the signer's account storage
        let newHello <- HelloWorld.createHelloAsset()

        acct.storage.save(<-newHello, to: /storage/HelloAssetTutorial)
  }

    // In execute, we log a string to confirm that the transaction executed successfully.
	execute {
        log("Saved Hello Resource to account.")
	}
}

このトランザクションの処理は以下の通りです。

  1. HelloWorldの定義をアカウント 0x06 からインポートします。
  2. createHelloAsset() 関数を使用してリソースを作成し、それを newHello に移動します。
  3. 作成したリソースを、トランザクションに署名したアカウントのストレージ( /storage/HelloAssetTutorialのパス)にsaveします。
  4. Saved Hello Resource to account.」というテキストをコンソールにログ出力します。

これは、prepareフェーズを使用した最初のトランザクションです。prepareフェーズとは、アカウント参照(&Account)を介して署名をしたアカウントにアクセスが出来る、唯一の場所です。アカウント参照は、アカウントとのやり取りに使用出来るさまざまなメソッドにアクセスすることが出来ます。一例として、アカウントのストレージがあります。上記の例では、トランザクションは auth(SaveValue) &Account というものを使用しており、これはSaveValueという認証権限(authorization entitlement)を意味しています。このトランザクションは、&Accountアカウント参照オブジェクトに対して、アカウントのストレージに値を保存することは出来るけど、それ以外の操作はできないということを意味しています。権限(Entitlement)については、後のチュートリアルで詳しく説明します。詳細については、権限に関するドキュメント(the entitlements documentation)を参照してください。

また、全アカウント権限(Entitlement)に関する可能な限りのドキュメントを、言語リファレンスのAccountセクションで見ることが出来ます。このチュートリアルでは、アカウントストレージの/storage/への保存やそこからロードを行うアカウントの関数を使用していきます。

アカウントはオブジェクトをパス(paths)に保存します。パスは基本的にアカウントのファイルシステムを表し、ユーザー定義のパスを使って、どこにでもオブジェクトを保存することができます。多くの場合、スマートコントラクトはそのスマートコントラクトのオブジェクトをどこに保存されるかをユーザーに明示しています。これによって、コードは標準的な方法でこれらのオブジェクトにアクセスする方法を知ることが出来ます。

executeフェーズにおけるアカウントストレージへのアクセスを禁止していること、および権限(Entitlement)の使用によって、任意のトランザクションが署名者アカウントのどのアセットや領域/パス(areas/path)を変更できるかを静的に検証可能にします。 トランザクションの送信を行うブラウザウォレットやアプリケーションは、これを使用して、トランザクションが変更するであろう内容をユーザーに表示することができます。これにより、ウォレットがユーザーの実行するトランザクションに関する情報をユーザーに提供し、アプリケーションやウォレットから悪意のある危険なトランザクションが送信されることはないという確信を与えることができます。

トランザクションについて、もう少し詳しく見ていきましょう。HelloAssetリソースを作成するために、スマートコントラクトのcreateHelloAsset()関数にアクセスし、それによって作成されたリソースをnewHello変数に移動させます。

let newHello <- HelloWorld.createHelloAsset()

次に、リソースをアカウントストレージに保存します。Flowでアカウントストレージとやり取りするには、アカウントストレージAPIを使用します。リソースを保存するには、アカウントストレージAPIのsave()メソッドを使用して、アカウントの/storage/HelloAssetTutorialパスにリソースを保存します。

acct.storage.save(<-newHello, to: /storage/HelloAssetTutorial)

saveに対する最初のパラメータは、保存されるオブジェクトであり、toパラメータはオブジェクトが保存されるパスです。パスはstorageパスでなければならず、toパラメータではドメインは/storage/のみが許可されます。

指定したパスにすでにオブジェクトが保存されている場合、プログラムは中止(abort)します。Cadenceの型システムにより、リソースが誤って失われることは決してないことを覚えておいてください。リソースをフィールド、配列、ディクショナリ、またはストレージに移動する場合、その場所にすでにリソースが含まれている可能性があります。Cadenceでは、既存のリソースを処理するように開発者を強制し、上書きによって誤って失われることがないようにしています。

また、あなたが識別子としてパス名をチョイスする時、プロジェクトに固有であるものを選ぶことが非常に重要です。現在、アカウントの保存パスはグローバルであるため、プロジェクトが同じ保存パスを使用する可能性があり、パスが競合する可能性があります。これは厄介な問題となる可能性があるため、このような問題を避けるために固有のパス名を選択してください。(補足: Cadenceではリソース宣言自体が一つの型であるため型自体は重複しないと考えられる)

最後に、executeフェーズでは、"Saved Hello Resource to account."という文言をコンソールにログ出力します。

log("Saved Hello Resource to account.")

ACTION
0x06アカウントを唯一の署名者として選択します。Sendボタンをクリックして、トランザクションを送信します。

以下のような表示がされるはずです。

"Saved Hello Resource to account."

ACTION
newHello をストレージに保存(save)するコード行の削除を試してもいいです。
newHello のエラーが表示されるはずです。そのエラーには「loss of resource」と表示されます。これは、リソースが適切に扱われていないことを意味します。プログラムでこのエラーが表示された場合、明示的に保存または破棄されていないリソースがどこかにあることを意味し、プログラムが不正であることを意味します。
トランザクションのチェックが適切になるようにするために、コード行を再追加します。

この場合、選択したアカウントで何かを保存するのは初めてなので、ストレージの/storage/HelloAssetTutorialの場所は空であることが分かります。実際のアプリケーションでは、誤って上書きをしようとしてトランザクションを中止しないように、保存する場所のパスで、必要なチェックとアクションを実行するでしょう。

トランザクションを実行したので、アカウント0x06のストレージには新たに作成されたHelloWorld.HelloAssetリソースが格納されているはずです。これを確認するには、左下のアカウント0x06をクリックします。これにより、アカウント内のさまざまなコントラクトとオブジェクトのビューが開きます。HelloWorldコントラクトとHelloAssetリソースのエントリが表示されるはずです。

Deployed Contracts:
[
  {
    "contract": "HelloWorld",
    "height": 6
  }
]
Account Storage:
{
    "Private": null,
    "Public": {},
    "Storage": {
        "HelloAssetTutorial": {
            "Fields": [
                39
            ],
            "ResourceType": {
                "Fields": [
                    {
                        "Identifier": "uuid",
                        "Type": {}
                    }
                ],
                "Initializers": null,
                "Location": {
                    "Address": "0x0000000000000005",
                    "Name": "HelloWorld",
                    "Type": "AddressLocation"
                },
                "QualifiedIdentifier": "HelloWorld.HelloAsset"
            }
        }
    }
}

FlowTokenオブジェクトも表示されているのがわかるでしょう。すべてのアカウントは、FlowTokenアセットを使用する能力が自動的に初期化されています(補足: FlowTokenはトランザクションフィーに必要なので全てのアカウントは初期化時にこれを保持します)。現時点では、それらについて心配する必要はありません。

Load Hello Transaction

次に、トランザクションでHelloAssetリソースのhello()メソッドを実行します。

ACTION
Load Helloという名前がついたトランザクションを開きます。
Load Helloには、以下のコードが含まれているはずです。

import HelloWorld from 0x06

// This transaction calls the "hello" method on the HelloAsset object
// that is stored in the account's storage by removing that object
// from storage, calling the method, and then saving it back to the same storage path

transaction {

    /// In this prepare block, we have to load a value from storage
    /// in addition to saving it, so we also need the `LoadValue` entitlement
    /// which additionally allows loading values from storage
    prepare(acct: auth(LoadValue, SaveValue) &Account) {

        // Load the resource from storage, specifying the type to load it as
        // and the path where it is stored
        let helloResource <- acct.storage.load<@HelloWorld.HelloAsset>(from: /storage/HelloAssetTutorial)
            ?? panic("The signer does not have the HelloAsset resource stored at /storage/HelloAssetTutorial. Run the `Create Hello` Transaction again to store the resource")

        // log the hello world message
        log(helloResource.hello())

        // Put the resource back in storage at the same spot
        acct.storage.save(<-helloResource, to: /storage/HelloAssetTutorial)
    }
}

このトランザクションは以下の処理を行います。

  1. HelloWorld定義を0x06アカウントからインポートします。
  2. HelloAssetオブジェクトを移動演算子とアカウントストレージAPIload関数を用いて、ストレージからhelloResourceに移動します。
  3. helloResourceに保存されたHelloAssetリソースのhello()関数を呼び出し、結果をログに記録します。
  4. リソースを、私たちが移動させる前にあったアカウントのパス/storage/HelloAssetTutorialに保存(save)し直します

私達はprepareフェーズを再び使用し、渡されるアカウントの参照を使ってリソースをloadします。

トランザクションについて、さらに詳しく見ていきましょう。

HelloAssetリソースをストレージからロードします。

ストレージからオブジェクトを取り除くには、アカウントストレージAPIのloadメソッドを使用します。

let helloResource <- acct.storage.load<@HelloWorld.HelloAsset>(from: /storage/HelloAssetTutorial)

指定した型のオブジェクトがパスに存在する場合は、この関数はそのオブジェクトを返します。そして、アカウントストレージには、指定されたパス以下のオブジェクトは含まれなくなります。

ロードするオブジェクトの型の、型パラメータは<> に含まれています。 ここで、私達は基本的に@HelloWorld.HelloAssetのリソースオブジェクトをこのパスからロードするということを述べています。 パラメータにある型の引数は明示的に指定する必要があります。 (リソースであることを意味する@記号に注目してください)

fromパスはストレージパスでなければなりません。そのため、/storage/ドメインのみが許可されます。

指定した型のオブジェクトが指定したパスに存在しない場合、この関数は何も返さない、すなわちnilを返します。これはオプショナル型といい、

オプショナル型とは、値の存在または非存在を表すことができる値です。オプショナル型には2つのケースがあります。指定した型の値があるか、または何も存在しない(nil)かのどちらかです。オプショナル型は、?接尾辞を用いて宣言します。

let newResource: HelloAsset?  // could either have a value of type `HelloAsset`
                              // or it could have a value of `nil`, which represents nothing

オプショナル型により、開発者はnilの場合をより適切に処理することができるようになります。ここで私達は、loadで取得したhelloResourceオブジェクトがnilである可能性をはっきりと考慮する必要があります(なぜなら、loadはロードするものがない場合はnilを返すからです)。

nil 結合演算子 (??) を使用して、オプショナルを「unwrap」します。これは基本的に私達は、loadメソッドがnilを返した場合の対応を処理することを意味します。もしnilが返ってきた時は、??の後のコードブロックが実行されます。 ここでは、panicを実行していまます。これはエラーメッセージとともにトランザクションの実行を中断します。

オプショナル型の詳細と使用方法については、「Optionals In Cadence」を参照してください。

コード内でもし何か問題が生じた時に、常にその詳細なエラーメッセージを開発者に提供することは、ユーザーや開発者にとって何が修正されるべきかを明白にする上で極めて重要です。

エラーメッセージには、可能であれば以下の内容を含めるべきです。

  • スマートコントラクトから由来した場合は、コントラクト名と関数名
  • 起きているリテラル エラー(the literal error)の説明
  • エラーの原因となっている可能性がある理由の高レベルな説明
  • エラーに直結している可能性があるメタデータまたは変数値
  • 可能ならば修正方法の提案

私たちのエラーメッセージを見てもらえば分かるとおり、私たちは問題点を正確に説明しています。つまり、リソースがしかるべき保存パスに保存されていないことを説明しています。そして、エラーを修正するための解決策を提案しています。それは、「Create Hello」トランザクションを実行してリソースを保存することです。

Flow NFTのGitHubリポジトリのスマートコントラクトトランザクションのエラーメッセージを参考に、詳細かつヘルプフルなエラーメッセージの例をチェックしてみてください。

hello()関数を呼び出す

次に、hello()関数を呼び出し、出力をログに出します。

log(helloResource.hello())

リソースを署名者のアカウントに再保存する

次に私達は、saveを再び使用して、オブジェクトを元のストレージの場所に戻します。

acct.storage.save(<-helloResource, to: /storage/HelloAssetTutorial)

ACTION
0x06アカウントを唯一の署名者として選択します。Sendボタンをクリックして、トランザクションを送信します。

以下のような表示がされるはずです。

"Hello, World!"

Reviewing the Resource Contract

このチュートリアルでは、アカウントストレージAPIを使用してトランザクションでリソースとのやり取りを行いながら、Cadenceのリソースの紹介を行いました。

あなたはすべてのスコープからアクセス可能なスマートコントラクトを実装しました。スマートコントラクトにはhello()という「Hello, World!」文字列を返す関数が実装されたリソースが宣言されてあり、また、リソースを作成できる関数を宣言しました。

次に、あなたはこのコントラクトをアカウントにデプロイし、スマートコントラクトの中でリソースを生成するトランザクションを作成し、そのリソースをトランザクションの署名者としていた0x06アカウントの中に保存しました。

最後に、トランザクションを使用してHelloAssetリソースをアカウントストレージから移動させ、helloメソッドを呼び出し、アカウントストレージに再び戻しました。

今ここであなたはこのチュートリアルを完了しましたので、シンプルなCadenceプログラムを書くための以下の基本的知識が身に付きました。

  • スマートコントラクトの中にリソースを実装する
  • アカウントストレージAPIと<-移動演算子を使用して、リソースを保存、移動、そしてロードすること
  • トランザクションのprepareフェーズを使用して、アカウントストレージからリソースをロードする(取り出す)こと

異なるリソースを作成するためにスマートコントラクトを好きなように変更してください、自由に利用可能なアカウントストレージAPIを実験してください、そして自由にあなたのスマートコントラクトの違う関数を実行する新しいトランザクションを作成してください。リソースでできることについてさらに詳しく知りたい場合は、リソースリファレンスページをご覧ください。

翻訳元->https://cadence-lang.org/docs/tutorial/resources

Flow BlockchainのCadence version1.0ドキュメント ()

Previous << 2. Hello World

Next >> 4. Capability Tutorial

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?