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 基礎編Part3

Last updated at Posted at 2024-12-30

Previous << 2 - 基礎編Part2
Next >> 4 - 基礎編Part4

TL;DR Flowブロックチェーンで何ができるのか?

Flowブロックチェーンのノードは世界中にあるのでビットコインやイーサリアムと同じように世界中で取引が可能です。

Crypto Kittiesというゲームを作った人が開発したブロックチェーンなのでゲームと相性が良く、自由にプログラミングができます

Flowブロックチェーンのスマートコントラクトは、そんなプログラミングコードが常に条件によって世界中で動くようになってます。

世界中で動くので常に平等に処理されることに注目してください。アフリカでも南極でも日本でもどこでも平等に処理されます。


それを使うと何ができるのか?というと、誰でもそれにアクセスして変更処理を書き加えることができます。

世界中の誰もがスマートコントラクトに対してトランザクション(情報更新処理)を行えるので、システムを作った人だけができる処理、というのが探してもありません。

それだと、スマートコントラクトを作った人のスマートコントラクトを誰でもその情報を書き換えられるって事?

はい、そうです。もしコントラクト内にpublic(=access(all))な関数があり、それがスマートコントラクトの情報の変更をするものであれば、誰もがそれの実行をできます。費用は0.001円未満です。

それではアプリを作るのに当然困るので、ちゃんとした機能があります。それがResourceです。日本語で資産という意味になりますが、資産には名前や等級があるように、Resourceもまたそれらステータスを呼び出すメソッドがあります。また、Resourceの持ち主はそれを通してスマートコントラクト内部の関数を呼び出すことができます。

それを使えば、出来ないことはありません。では一緒にResourceを使ってアプリを作ってみましょう!

Why Flow

FLOW(または$FLOW)トークンは、Flowネットワークのネイティブ通貨です。開発者およびユーザーは、FLOWを使用してネットワーク上で取引(transact)を行うことができます。開発者は、ピアツーピア決済(他人同士の決済)、サービス料金徴収、または消費者向け特典(rewards)のために、FLOWを直接アプリに統合することができます。

ということでやっていきます、P2P(ピアツーピア)決済アプリ開発!

💡もし、エミュレータの起動方法やスマートコントラクトのデプロイについて操作に自信がない場合はこちらを参照してください。

Resource, Capability そして Entitlement

Resourceは一種のオブジェクトで、スマートコントラクトの中でだけ生成できます。それをアカウントのストレージに保存します。このアカウントのストレージにはアカウントが鍵を使うことによってのみアクセスできます。

これだけでは少し不便です。あなたがゲームデベロッパーだとして、このResourceオブジェクトのIDを知りたいと思います。それがアカウントの鍵によってしか取得できなければ、ゲーム進行が難しくなります。

そこで誰でもアクセスして良い機能(IDなどフィールドの読み取りを行う)をアカウントのPublicストレージにCapabilityとして公開・保存します。

一度アカウントのCapabilitiesのパス(/public/の下)に保存されれば、誰でもその情報にアクセスすることが出来ます。

Entitlementは、トランザクションでアクセスを可能にする機能を細かく設定することが出来ます。例えばchangeGreeting関数に誰でもアクセス出来ては困る場合、greetingフィールドにだけアクセスが可能なCapabilityを作成することができます。

スマートコントラクトにResourceEntitlementを記述する

Day1の時のスマートコントラクトは以下のようになっていました。

HelloWorld.cdc
access(all) contract HelloWorld {

  access(all) let greeting: String

  init() {
    self.greeting = "Hello, World!"
  }
}

これに対して、リソースを実装したいので、以下のように修正します。(リソースを保存したアカウントは、リソース関数であるchangeGreeting関数を呼び出したり、リソースのgreetingフィールドを読み取ることが出来ます)

HelloWorld.cdc
access(all) contract HelloWorld {

  access(all) entitlement ChangeGreeting

  access(all) resource Greeting {
    access(all) var greeting: String

    access(ChangeGreeting) fun changeGreeting(newGreeting: String) {
      self.greeting = newGreeting
    }
    init(greeting: String) {
      self.greeting = greeting
    }
  }

  access(all) fun createGreeting(greeting: String): @Greeting {
    return <- create Greeting(greeting: greeting)
  }
}

変更箇所は以下です。

  • ChangeGreetingというentitlementを作成する
  • Greetingというresourceを作成し、その中にHelloWorldコントラクトの中のフィールドやinit関数を移動する
  • greetingフィールドは変更可能である必要があるので、letからvarに変更する
  • changeGreeting関数をChangeGreeting entitlementのアクセス権で新規に作成する
  • HelloWorldコントラクトにcreateGreetingというresourcecreateする関数を作成する

変更したスマートコントラクトをデプロイする

エミュレータが起動している状態で、以下を実行します。

flow project deploy

Output:

Deploying 1 contracts for accounts: emulator-account

HelloWorld -> 0xf8d6e0586b0a20c7 (6acbe99c6fb968760219224e3a81d33f9e988ccee02864396f6386f0fca3799c) 

🎉 All contracts deployed successfully

resourceを作成して、アカウントのストレージに保存する

HelloWorld.cdcファイルと同じ階層にstore_resource.cdcファイルを作成します。

以下のコードをstore_resource.cdcに記載します。

store_resource.cdc
import "HelloWorld"

transaction(greeting: String) {
  prepare(signer: auth(Storage) &Account) {
    signer.storage.save(<- HelloWorld.createGreeting(greeting: greeting), to: /storage/MyGreeting)
  }
}

これを実行すると、Greetingリソースがアカウントのストレージに保存されます。

Flow CLIにはトランザクションを送信してくれるコマンドが用意されているので、それを使用してトランザクションを実行しました。

flow transactions send ./store_resource.cdc "Hello"

Output:

Transaction ID: b2ee6b02e41da2ec96d4930894324fc924b546e09b79af7411d109503e555947

Block ID        6ac4f5bdd49b29c8f73081db0d1104a8657c9b4815d7282bff4ba5c686cac42f
Block Height    2
Status           SEALED
ID              b2ee6b02e41da2ec96d4930894324fc924b546e09b79af7411d109503e555947
Payer           f8d6e0586b0a20c7
Authorizers     [f8d6e0586b0a20c7]

Proposal Key:
    Address     f8d6e0586b0a20c7
    Index       0
    Sequence    1

Capabilityを保存するコードを追加する

このままではScriptsでgreetingリソースの情報にアクセスできないので、Capabilityを発行し、ストレージの/public/パスの下に保存します。

store_resource.cdcのコードにCapabilityを保存するコードを追加します。

store_resource.cdc
import "HelloWorld"

transaction(greeting: String) {
  prepare(signer: auth(Storage, Capabilities) &Account) {
    signer.storage.save(<- HelloWorld.createGreeting(greeting: greeting), to: /storage/MyGreeting)
    let cap = signer.capabilities.storage.issue<&HelloWorld.Greeting>(/storage/MyGreeting)
    signer.capabilities.publish(cap, at: /public/MyGreeting)
  }
}

Cadenceではすべてのリソースは型を持っており、<&HelloWorld.Greeting>の部分はその型を表しています。つまり、/public/MyGreetingにこれから保存されるCapabilityはHelloWorld.Greetingの型を持ちます。(スマートコントラクトが異なれば同名であっても型は異なります)

この状態で再度以下のコマンドを実行します。すると、既に同じパスにリソースが保存されている内容のエラーが表示されます。リソースをストレージのパスに対して上書きして保存できないためです。最初からリソースをストレージに保存する処理とCapabilityを公開する処理を書いて同時に説明すればよかったのですが、新しい概念を2つ同時に説明する文章力がありませんでした。
一度リソースを取り出せば同じパスに保存することができますが、リソースはソースコード内に放置して消滅させることができません。ストレージに保存するかdestroyキーワードを使用して明示的に破棄する必要があります。そうしないとトランザクションが失敗します。

flow transactions send ./store_resource.cdc "Hello"

Output:

Transaction ID: ede2d376847f97fded05dcc3db7d2e019900515ca186a1401fba487e7355e3c7

Block ID        19761dfc060fafa98273d2ec9dfc0276f22fcdfb163101cf42a5bad3f8f60b6d
Block Height    3
 Transaction Error 
[Error Code: 1101] error caused by: 1 error occurred:
        * transaction execute failed: [Error Code: 1101] cadence runtime error: Execution failed:
error: failed to save object: path /storage/MyGreeting in account 0xf8d6e0586b0a20c7 already stores an object
 --> ede2d376847f97fded05dcc3db7d2e019900515ca186a1401fba487e7355e3c7:5:4
  |
5 |     signer.storage.save(<- HelloWorld.createGreeting(greeting: greeting), to: /storage/MyGreeting)
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ストレージに保存されたリソースを削除するには新しいトランザクションを作成する必要があるので、一度エミュレータを止めて、エミュレータ起動とコントラクトデプロイをやり直します。(そうするとエミュレータアカウントのストレージに保存されたリソースは消えてなくなります)

そして再度flow transactions send ./store_resource.cdc "Hello"コマンドを実行します。

Output:

Transaction ID: 4249eefed0b4e276ebf3eac8a36811f0b853e6912dcdabe22db98cadd80006a1

Block ID        ca6eb712dae839370f4dafc2f80e34cdeebfb88c0d0f1e0d88ebc9362654c0dd
Block Height    2
Status           SEALED
ID              4249eefed0b4e276ebf3eac8a36811f0b853e6912dcdabe22db98cadd80006a1
Payer           f8d6e0586b0a20c7
Authorizers     [f8d6e0586b0a20c7]

Proposal Key:
    Address     f8d6e0586b0a20c7
    Index       0
    Sequence    1

これでgreetingというresource/storage/MyGreetingのパスに、リソースのCapabilityを/public/MyGreetingパスに保存することが出来ました。

Capabilityにアクセスしてフィールドの情報を取得する

Capabilityの中には、greetingというaccess(all)
アクセス権のフィールドと、access(ChangeGreeting)アクセス権の関数がありますが、Capabilityをパスに保存する時にChangeGreeting Entitlementを指定していませんので、関数の方には誰もアクセスできません。

Day1で作成したread_greeting.cdcのコードを以下のように変更します。

read_greeting.cdc
import "HelloWorld"

access(all) fun main(user: Address): String {
    let greetingRef: &HelloWorld.Greeting =
      getAccount(user).capabilities.borrow<&HelloWorld.Greeting>(/public/MyGreeting)
        ?? panic("This public capability does not exist.")

    return greetingRef.greeting
}

&は参照であることを意味します。(C言語のポインタのようなもの)

以下コマンドをTerminalウィンドウで実行します。

flow scripts execute ./read_greeting.cdc "0xf8d6e0586b0a20c7"

Output:

% flow scripts execute ./read_greeting.cdc "0xf8d6e0586b0a20c7"

Result: "Hello"

"0xf8d6e0586b0a20c7"はリソースを保存しているユーザーのアドレスです。Scriptsmain関数から始まりますが、大抵の場合、アドレスをこのmain関数の引数にセットします。アドレスさえ分かればgetAccount(user).capabilities.borrowでCapabilityの参照を取得できるからです。そのアドレスをCLIのコマンドに引数として渡しています。ちなみにgetAccountはScripts限定(読み取り専用)です。Transactionsで実行するとエラーになります。これは私もDiscordの開発者チャンネルで聞いて知りましたので不明点はDiscordで直接聞いてみるといいでしょう。

アカウントの鍵を取得してchangeGreetingを呼び出すトランザクションを実行する

HelloWorld.cdcファイルと同じ階層にchange_greeting.cdcファイルを作成します。

以下のコードをchange_greeting.cdcに記載します。

change_greeting
import "HelloWorld"

transaction(greeting: String) {
    prepare(signer: auth(Storage, Capabilities) &Account) {
        let g =
            signer
            .storage
            .borrow<auth(HelloWorld.ChangeGreeting) &HelloWorld.Greeting>(from: /storage/MyGreeting)
            ?? panic("A resource does not exist here.")
        g.changeGreeting(newGreeting: greeting)
    }
}

スクリーンショット 2024-12-30 11.15.13.png
auth()の中に書かれているのはEntitlementです。デフォルトでStorage, Capabilities, Keys, Contractsに対するEntitlementが多数用意されています。ここでは横着してStorageをセットしましたが、これらの名前のは権限が強いのでもっと弱い権限をセットするべきだと思います。Interaction Templatesなどでここの情報が表示されるはずなので、権限が強いEntitlementはお勧めされません。(Capabilitiesも渡しているけど多分必要ないですね.. Entitlementに関しては新機能なので私も模索しながら実装していました。)

以下のコマンドをTerminalウィンドウで実行し、トランザクションを送信します。

flow transactions send ./change_greeting.cdc "Goodbye."

Output:

Transaction ID: f50441805a3c745e55620d8c6a9cf36ae8b0032fcb0bcb997a6a9899dfdf2504

Block ID        3d6534c78d88d01ec4c9e65dbf5bccd2c8026c20592d37d74fd78f22772aead1
Block Height    3
Status           SEALED
ID              f50441805a3c745e55620d8c6a9cf36ae8b0032fcb0bcb997a6a9899dfdf2504
Payer           f8d6e0586b0a20c7
Authorizers     [f8d6e0586b0a20c7]

Proposal Key:
    Address     f8d6e0586b0a20c7
    Index       0
    Sequence    2

トランザクションで変更されたリソースのstateを読み取る

再度以下コマンドをTerminalウィンドウで実行し、greetingのstateを読み取るScriptsを実行します。

flow scripts execute ./read_greeting.cdc "0xf8d6e0586b0a20c7"

Output:

% flow scripts execute ./read_greeting.cdc "0xf8d6e0586b0a20c7"

Result: "Goodbye."

リソースのgreetingフィールドのstateがGoodbye.に変更されていることを確認できました。このリソースはアカウントのストレージに保存されているものなので、リソースの情報が変更されたことを確認できたことになります。Day2の冒頭で触れた戦士リソースであればHPを減らす処理などが先ほどのトランザクションスマートコントラクトでできることになります。


Previous << 2 - 基礎編Part2

Flow blockchain / Cadence version1.0ドキュメント

Next >> 4 - 基礎編Part4

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?