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?

How to develop a blockchain game. Day3 基礎編Part3

Last updated at Posted at 2024-12-30

Previous << Day2 - 基礎編Part2
Next >> Day4 - 基礎編Part4

Why Flow

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

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

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

Resource, Capability そして Entitlement

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

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

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

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

Entitlementは、トランザクションでアクセスを可能にする機能を細かく設定出来ます。

ResourceおよびCapabilityの内容

こちらの実装を元にして、ResourceにgreetingフィールドとchangeGreeting関数を作成します。

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)
  }
}

変更箇所は以下です。(読み飛ばしてOKです。書いてある事はコントラクト関数/フィールドをリソース関数/フィールドに変えました、というだけです。)

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

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

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

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を保存するコードを追加する。

*Cadenceではすべてのリソースは型を持っているので、<&HelloWorld.Greeting>の部分はその型を表しています。つまり、/public/MyGreetingに保存されたCapabilityはHelloWorld.Greetingの型を持っています。(この型にはEntitlementは含まれていません。)

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)
  }
}

この状態で再度以下のコマンドを実行すると、既に同じパスにリソースが保存されている内容のエラーが表示されます。

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)のアクセス権限の関数がありますが、パス保存時に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
}

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

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

Output:

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

Result: "Hello"

"0xf8d6e0586b0a20c7"はリソースを保存しているユーザーのアドレスです。このアドレスはウォレットにサインインしていれば誰でも取得が可能なので、このアドレスを利用してリソースのIDなど必要な情報をjavascriptで取得して開発を進めていきます。

最後にアカウントの鍵を取得して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 capability does not exist here.")
        g.changeGreeting(newGreeting: greeting)
    }
}

スクリーンショット 2024-12-30 11.15.13.png

以下コマンドを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ウィンドウで実行します。

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

Output:

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

Result: "Goodbye."

Previous << Day2 - 基礎編Part2

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

Next >> Day4 - 基礎編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?