ブロックチェーンゲームの作り方3 基本編Part3

[Previous << 2 - 基本編Part2](https://qiita.com/flowcadence/items/bd9dd37723dab0557064)
[Next >> 4 - 基本編Part4](https://qiita.com/flowcadence/items/27ef976c2f2493146dd6)

https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a#%E9%96%8B%E7%99%BA%E6%96%B9%E6%B3%95

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

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

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

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

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

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

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

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

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

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

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

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

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

:::note warn
💡もし、エミュレータの起動方法やスマートコントラクトのデプロイについて操作に自信がない場合は[こちら](https://qiita.com/flowcadence/items/8cc25249cda37364cac1)を参照してください。
:::

#### Resource, Capability そして Entitlement

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

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

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

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

`Entitlement`は、トランザクションで[アクセスを可能にする機能を細かく設定する](https://qiita.com/flowcadence/items/572df270135946f5290e)ことが出来ます。例えば`changeGreeting`関数に誰でもアクセス出来ては困る場合、`greeting`フィールドにだけアクセスが可能なCapabilityを作成することができます。

#### スマートコントラクトに`Resource`と`Entitlement`を記述する

Day1の時のスマートコントラクトは以下のようになっていました。
```ruby:HelloWorld.cdc
access(all) contract HelloWorld {

  access(all) let greeting: String

  init() {
    self.greeting = "Hello, World!"
  }
}
```
これに対して、リソースを実装したいので、以下のように修正します。(リソースを保存したアカウントは、リソース関数である`changeGreeting`関数を呼び出したり、リソースの`greeting`フィールドを読み取ることが出来ます)
```ruby: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という`resource`を`create`する関数を作成する

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

エミュレータが起動している状態で、以下を実行します。
```ruby
flow project deploy
```
Output:
```ruby
Deploying 1 contracts for accounts: emulator-account

HelloWorld -> 0xf8d6e0586b0a20c7 (6acbe99c6fb968760219224e3a81d33f9e988ccee02864396f6386f0fca3799c) 

🎉 All contracts deployed successfully
```

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

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

以下のコードを`store_resource.cdc`に記載します。
```ruby: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にはトランザクションを送信してくれるコマンドが用意されているので、それを使用してトランザクションを実行しました。
```ruby
flow transactions send ./store_resource.cdc "Hello"
```
Output:
```ruby
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`](https://qiita.com/flowcadence/items/ef8373ed9471362f7132)でgreetingリソースの情報にアクセスできないので、`Capability`を発行し、ストレージの`/public/`パスの下に保存します。

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

```ruby: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)
  }
}
```

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

この状態で再度以下のコマンドを実行します。すると、既に同じパスにリソースが保存されている内容のエラーが表示されます。リソースをストレージのパスに対して上書きして保存できないためです。<sup>最初からリソースをストレージに保存する処理とCapabilityを公開する処理を書いて同時に説明すればよかったのですが、新しい概念を２つ同時に説明する文章力がありませんでした。</sup>
一度リソースを取り出せば同じパスに保存することができますが、リソースはソースコード内に放置して消滅させることができません。ストレージに保存するか`destroy`キーワードを使用して明示的に破棄する必要があります。<sup>そうしないとトランザクションが失敗します。</sup>
```ruby
flow transactions send ./store_resource.cdc "Hello"
```
Output:
```ruby
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:
```ruby
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`にアクセスしてフィールドの情報を取得する

:::note warn
*Capabilityの中には、greetingという`access(all)`
アクセス権のフィールドと、`access(ChangeGreeting)`アクセス権の関数がありますが、Capabilityをパスに保存する時に`ChangeGreeting Entitlement`を指定していませんので、関数の方には誰もアクセスできません。*
:::
`Day1`で作成した`read_greeting.cdc`のコードを以下のように変更します。
```ruby: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ウィンドウで実行します。
```ruby
flow scripts execute ./read_greeting.cdc "0xf8d6e0586b0a20c7"
```
Output:
```ruby
% flow scripts execute ./read_greeting.cdc "0xf8d6e0586b0a20c7"

Result: "Hello"
```
:::note warn
`"0xf8d6e0586b0a20c7"`はリソースを保存しているユーザーのアドレスです。[`Scripts`](https://qiita.com/flowcadence/items/ef8373ed9471362f7132)は`main`関数から始まりますが、大抵の場合、アドレスをこの`main`関数の引数にセットします。アドレスさえ分かれば`getAccount(user).capabilities.borrow`でCapabilityの参照を取得できるからです。そのアドレスをCLIのコマンドに引数として渡しています。ちなみに[`getAccount`](https://qiita.com/flowcadence/items/5ddbb79fe8438850cf57#account-access)はScripts限定(読み取り専用)です。[`Transactions`](https://qiita.com/flowcadence/items/318a428e8c006dc2497f)で実行するとエラーになります。<sup>これは私もDiscordの開発者チャンネルで聞いて知りましたので不明点は[Discord](https://discord.com/invite/flow)で直接聞いてみるといいでしょう。</sup>
:::
#### アカウントの鍵を取得してchangeGreetingを呼び出すトランザクションを実行する

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

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

```ruby: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3912821/32796fc7-7370-f76e-1150-ed513e22144b.png)
`auth()`の中に書かれているのは[Entitlement](https://qiita.com/flowcadence/items/572df270135946f5290e#entitlements)です。デフォルトで[Storage](https://qiita.com/flowcadence/items/129d58ae7cb962056c6c#accountstorage), [Capabilities](https://qiita.com/flowcadence/items/b70151356f4e548f2c91#accountcapabilities), [Keys](https://qiita.com/flowcadence/items/19710392d7fb55545db4#accountkeys), [Contracts](https://qiita.com/flowcadence/items/c8661fbf9eae32ce5926#accountcontracts)に対するEntitlementが多数用意されています。ここでは横着して`Storage`をセットしましたが、これらの名前のは権限が強いのでもっと弱い権限をセットするべきだと思います。Interaction Templatesなどでここの情報が表示されるはずなので、権限が強いEntitlementはお勧めされません。(`Capabilities`も渡しているけど多分必要ないですね.. Entitlementに関しては新機能なので私も模索しながら実装していました。)

以下のコマンドをTerminalウィンドウで実行し、トランザクションを送信します。
```ruby
flow transactions send ./change_greeting.cdc "Goodbye."
```
Output:
```ruby
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を実行します。
```ruby
flow scripts execute ./read_greeting.cdc "0xf8d6e0586b0a20c7"
```
Output:
```ruby
% flow scripts execute ./read_greeting.cdc "0xf8d6e0586b0a20c7"

Result: "Goodbye."
```
リソースのgreetingフィールドのstateが`Goodbye.`に変更されていることを確認できました。このリソースはアカウントのストレージに保存されているものなので、リソースの情報が変更されたことを確認できたことになります。[Day2](https://qiita.com/flowcadence/items/bd9dd37723dab0557064)の冒頭で触れた戦士リソースであればHPを減らす処理などが先ほどの[トランザクション](https://qiita.com/flowcadence/items/2911220eb06a20beea14#%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E9%8D%B5%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%81%A6changegreeting%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B)と[スマートコントラクト](https://qiita.com/flowcadence/items/2911220eb06a20beea14#%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%82%B3%E3%83%B3%E3%83%88%E3%83%A9%E3%82%AF%E3%83%88%E3%81%ABresource%E3%81%A8entitlement%E3%82%92%E8%A8%98%E8%BF%B0%E3%81%99%E3%82%8B)でできることになります。

***
<b>[Previous << 2 - 基本編Part2](https://qiita.com/flowcadence/items/bd9dd37723dab0557064)
</b>

[Flow blockchain / Cadence version1.0ドキュメント](https://qiita.com/flowcadence/items/3073d69c6e9895e68b5a#%E9%96%8B%E7%99%BA%E6%96%B9%E6%B3%95)

<b>[Next >> 4 - 基本編Part4](https://qiita.com/flowcadence/items/27ef976c2f2493146dd6)</b>