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を作成します。
スマートコントラクトにResource
とEntitlement
を記述する
Day1の時のスマートコントラクトは以下のようになっていました。
access(all) contract HelloWorld {
access(all) let greeting: String
init() {
self.greeting = "Hello, World!"
}
}
これに、各アカウントにリソースを保存できる機能を追加するために、以下のように修正します。(リソースを保存したアカウントは、リソース関数であるchangeGreeting
関数を呼び出したり、リソースのgreeting
フィールドを読み取ることが出来ます)
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
に記載する
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
は含まれていません。)
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
のコードを以下のように変更します。
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
に記載する
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)
}
}
以下コマンドを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."