Tezos DApps ハッカソン
Jun FURUSE/古瀬 淳
Nagoya Web3 Hackathon, Nagoya, 2022-11-20
自己紹介
古瀬 淳
- ダイラムダ株式会社
- Tezos blockchain のコア開発者の一人
ダイラムダ株式会社
ブロックチェーン技術の研究開発
- 研究開発が中心
-
パブリック分散DB の技術開発。
クリティカルシステムとしての安全性の追求。
アプリケーションへのコンサル業務。
- 投機には興味がない
-
手を出すと本業に集中できなくなるので、
暗号通貨は一切所有していません。
分散DBとしてのブロックチェーン
- 台帳方式
-
差分を積み上げる (like Git)
- パブリック、オープン
-
誰でもDBネットワークに参加できる。
- 非中央集権
-
特権を持つ中央管理者がいない。分散しているので落ちにくい。
- トラストレス
-
他人を盲信する必要がない。
無から中立な信用システムを形成できる夢がある。
バリデータ
ブロックチェーンの維持を行う参加者
(Miner、Bakerともいう)
- トランザクション要求をブロックにまとめる
- 他のバリデータからのブロックが真正なものか確認する
- バリデータ間での投票で合意が取れたブロックがチェーンに付け加わる
- 誰でもバリデータになれる
オープンネットワークでの投票、信用できますか?
なりすまし放題だと、投票が成り立たない: Sybil 攻撃
Proof of なんとか
Sybil 攻撃対策: 投票権を有限の何かで制限する:
- Proof of Work: 計算資源
- Proof of Stake: 過去のトークン保有量
それぞれに pros, cons があるが、流れは PoS でしょう
インセンティブ設計
バリデータに正直な行動をさせるには…価値が必要
- 正直な行動に対して、その費用+α を報いる
- 嘘をついたり妨害しないように、バリデータから保証金を取っておく
- 他のバリデータが不正を指摘すると保証金は没収
- 指摘したバリデータには報奨が出る
- 原資はDB使用料 (fee)
仮想通貨の誕生。
仮想通貨としてのブロックチェーン
- 通貨
-
パブリック分散DBを成り立たせる
インセンティブ設計のためにどうしても必要。
- 高い安全性要求
-
脆弱性は即通貨の大量窃盗につながる。
安全性を保障したクリティカルシステムに向けて、
多くのサイエンスとエンジニアリングのチャレンジがある。
Tezos
ブロックチェーンのひとつ:
- LPoS: マイニングに大きな電力を必要としない (Baking)
- 使用費用が安い
- スマートコントラクトによる自動実行/契約: DApps
- 現在 L2 で大規模スケーリングを行なっている
(今回は関係ありません)
DApps
Decentralized Appications
ブロックチェーン、スマートコントラクトを使ったアプリ:
- 価値のやり取りができる
- 非中央集権
- コードがオープン
- 誰でも検証可能
- ズルできない(皆がちゃんと検証すれば😜
DApps の難しさ
- 遅い (30秒/1ブロック)
- 高い
- オープンなので秘密をそのまま置けない (暗号化が必要
- バグを突かれると金を盗まれる
- 道具が特殊
どんな Tezos DApps がある?
- NFT: アートNFTなど
- DEX: 分散取引所
- Name service: DNS
- ゲーム
Tezos DApps: NFT
アートNFTが流行っている
デジタルアートのデータをトークン化して売買
Non native token
スマートコントラクト上で実装されているトークン
(ブロックチェーン運営で使っているトークンは Native token)
普通の貨幣と同じような性質:
- 総量の保存 (勝手に鋳造できない)
- 所有者/所有量
- 支払い
NFT
- Fungible Token
-
代替可能 ex. あなたと私の10円玉
- Non Fungible Token
-
代替不可能 ex. カードゲームのカード
実装
- Fungible Token
-
- Non Fungible Token
-
- 誰が<何を><どれだけ>持っているか
- 複数の FT を一つのスマートコントラクトで扱うのと同等
デジタルアート NFT とオフチェーンストレージ
デジタルアートデータは大きすぎ、チェーンに載せられない
(載せてもいいが高い: Tezos でさえ 1byte = 0.04円)
そこでアートデータの hash だけをチェーンに載せる
アートデータ自体はオフチェーンに
- IPFS に置くのが主流。
- オフチェーンデータの永続性に注意
- この手の NFT
を買ったらオフチェーンデータは手元に取っておきましょう
デジタルアート NFT ?
- 常にある疑問
-
誰でもアクセスできるデータの hash を<所有>することの意味
🤔 投げ銭以上の意味あんのかな?
これからは、hash の所有者に +α の価値を提供できるか
Tezos トークン規格 FA2
FT, NFT 併せたインターフェースを定義。
FA2 準拠 FT/NFT は対応ウォレット+交換所+DApps で扱える
Tezos DApps: DEX
分散取引所
仮想通貨同士の交換を提供する
- QuipuSwap
- Plenty
- SpicySwap
- etc
Tezos DApps: ゲーム
いろいろあるようです:
ゲームできてないのに出資集めて開発しているものがありますね…
まあクラファンみたいなものか。
ブロックチェーンは遅いし高い。
ゲームでの意味のある使用には工夫が必要
Tezos でどんな DApps を作るか
何でもいいです、できたら:
- 普通の中央集権 DB ではできない事
- 暗号通貨でのやりとり (本気でやると税とか大変だけど
- オープンになっていることの魅力
- ブロックチェーンやネットの外の世界と繋がっていると面白い
Tezos ブロックチェーンの基本
- 普通の DB と比べれば遅い: 更新単位時間 30秒
- 記憶域は高い: 1byte = 0.04円
- 各アカウントはトークンを保持しており、使用料はそこから支払われる
- 公開鍵暗号によるアカウント認証
- 秘密鍵を知っている人がオーナー
- 公開鍵はチェーン上に公開(reveal)
- アカウント操作が正当であることを公開鍵で検証
- スマートコントラクト: 特殊なアカウント。
- トークン保有量の他に、コントラクトコード、データを持つ
- 送金をトリガーにコントラクトコードを実行
Tezos でのチェーン操作の流れ
- ユーザー/ が送金などの op を作る
- 秘密鍵を持つウォレットが op に署名
- ウォレットが署名付き op を mempool に入れる
- バリデータが mempool 内の ops を取り上げブロックを作成
- バリデータがブロックをチェーンの先頭に登録
- バリデータ達が新ブロックに裏書き(endorsing)投票を行う
- ブロックが確定
DApps の構造
- On chain: スマートコントラクト
-
金銭取引、非中央集権性、認証、改竄不可能性を提供
- Off chain: ブロックチェーンが提供しない部分
-
デスクトップ、スマホ、ブラウザ アプリや、補助 DB など
- ウォレット
-
Off chain プログラムがチェーンに対して行いたい操作に対し公開鍵署名を行う
Tezos DApps の流れ
- DApps はRPCで情報を読み取る
- ユーザーからの操作を受け、スマートコントラクト呼び出しパラメータを決定
- DApps は op をユーザーウォレットに送る
- ウォレットはユーザーの秘密鍵で署名を行い、チェーンに送る
- スマートコントラクトは op
のパラメータを受け取ってコードを実行する
- DApps はブロックチェーンに op が取り込まれたのを確認する
Tezos での DApps 開発
- ウォレット
- テストネット
- ブロックエクスプローラ
- Tezos スマートコントラクトの構造
- スマートコントラクト記述とデプロイ (SmartPy)
- オフチェーンプログラミング (Taquito)
ドキュメント
いろいろありますが、公式のものが一番良い:
今日扱うツール関連
全て英語です。キツイ人は自動翻訳使ってください。
Tezos ネットワーク
- メインネット
-
トークンに金銭価値がある。 購入したければ自己責任でお願いします:
- テストネット
-
トークンは無料で配布。
テストネットで開発し、完成したらメインネットにデプロイ。
今日はテストネットしか使いません。
Tezos テストネット
複数のテストネットがあります。
- 長期テストネット: Ghostnet
- 新プロトコルテスト用: Limanet, etc.
Ghostnet をお勧め。
ブロックタイムは 15秒。(メインネットは 30秒)
ウォレット
なんでもいいですが、必ず公式推奨の物を:
- 演習
-
Temple wallet をインストールする
アカウントの作成
- Seed phrase (どこかに記録しておく)
- Password (脳に覚えておく)
- サブアカウントの作成
- 同一 seed phrase と password から複数のアカウントを導出できる
- 注意
-
秘密鍵、アカウント名はメイン/テストネットで共通
- 複数ネットでのアカウントの使い回しは危険!!
- 適切な名前をつける “Test1”, “Test2”, “Main big”, “Main small”,
etc
- 演習
-
Temple wallet でアカウントを二つ作る
テストネットのトークンを入手
Faucet から取得:
- 演習
-
Ghostnet faucet からテストトークンを取得
ブロックエクスプローラー
ブロックチェーンやアカウントの状態を確認
TzStats など
- 演習
-
Faucet からのテストトークン取得を TzStats で確認
トークンの移動
サブアカウントを作成し、トークンを移動
- Confirmation を待つ
- TzStats で確認
トークン移動がブロックチェーンに記載されるまで少し時間が必要
- ブロックタイム: ブロック更新単位時間 (テストネットでは15秒)
- Baker が動いていない場合さらに遅れることがあります
- 演習
-
トークンをサブアカウントに送金し、結果を確認
Tezos のスマートコントラクト
Michelson VM というスタックマシンを使用
Michelson は人間には辛いので高級言語を使う
Tezos のスマートコントラクト
- ストレージ
-
記憶領域です。値を一つ取ります
Map や Big_map
を使うと巨大なデータを保存できますが、
記憶域料金がかかります。これが Tezos
での一番のコストになります。
ご利用は計画的に。
- エントリポイント
-
複数のプログラム実行エントリポイントを持てます。外部から機能の呼び分けが可能です。
- スマートコントラクトの動作
-
エントリポイントはオペレーションからパラメータを受け取り、コードを実行し、
を行います
SmartPy
https://smartpy.io
Python で Tezos スマートコントラクトを書くと、
Michelson
にコンパイルしてくれる。
私は Python 書いたことありません
簡単な SmartPy コード
import smartpy as sp
class MyContract(sp.Contract):
def __init__(self, initx):
self.init(x = initx)
@sp.entry_point
def add(self, addx):
self.data.x += addx
@sp.add_test(name = "MyTest")
def test():
scenario = sp.test_scenario()
contract = MyContract(1)
scenario += contract
scenario += contract.add(2)
簡単な SmartPy コード: import
sp
という名前でライブラリを使用:
SmartPy: クラス
コントラクトは sp.Contract
のサブクラスとして定義:
class MyContract(sp.Contract):
...
SmartPy: ストレージ初期化
__init__
でデプロイ時のストレージの初期化を記述:
def __init__(self, initx):
self.init(x = initx)
一般的には:
def __init__(self, 値1, 値2, ...):
self.init(ストレージ変数1 = 値1, ストレージ変数2 = 値2, ...)
self.init(..)
(ストレージ初期化)以外のことはできません
SmartPy: エントリポイント
エントリポイントは @sp.entry_point
アトリビュート付きのメソッドとして書く:
# ストレージ x の値を増す
@sp.entry_point
def add(self, addx):
self.data.x += addx
一般的には:
@sp.entry_point
def メソッド名(self, 引数1, 引数2, ...):
... コード ...
- ❗️️️
-
@sp.entry_point
を忘れがち。忘れると、
Error: New command outside of contract
などと言われる
SmartPy: ストレージ変数へのアクセス
self.data.ストレージ変数名
でアクセスする:
self.data.x += addx # 加算
self.data.x = self.data.x * 2 # 二倍
より一般的には:
- ❗️️️️
-
.data
を忘れがち。忘れると、
よく分からない
unused parameter
エラーが出る。
SmartPy IDE: テスト
テストを記述すると、
- IDE からデプロイが可能になる
- 動作をシミュレートできる
@sp.add_test(name = "MyTest") # これはテストですよ
def test():
scenario = sp.test_scenario() # テストシナリオの作成
contract = MyContract(1) # コントラクトの生成
scenario += contract # コントラクトのデプロイ
scenario += contract.add(2) # エントリポイントの呼び出し
SmartPy IDE: コードのテストラン
▶︎ を押すとコードをチェック、問題がなければテストを行う。
右側にテストの実行結果が表示される。
- 演習
-
簡単な SmartPy コードを走らせ、テスト結果を見る。
コードは自分で書くか、テンプレートの Simple Examples お勧め
SmartPy IDE: デプロイ
Deploy Michelson Contract > Deploy Michelson Contract:
- 正しいネットワークを選ぶ (Ghostnet)
- Temple を選んで使用するアカウントを選ぶ
- 事前に “REVEAL ACCOUNT” が必要な場合があります
- ❗️ “ESTIMATE COST FROM RPC” で正しいコストを見積もる
- デフォルトの値では足りない場合があり、不足するとエラーなしに失敗する
- “DEPLOY CONTRACT”
- Block Confirmations を待つ
- “OPEN EXPLORER” で確認
- “SAVE CONTRACT” でアドレスを IDE に保存
- 演習
-
コントラクトをデプロイしてみる
SmartPy IDE: コントラクトの呼び出し
OPEN EXPLORER > INTERACT でエントリポイントを呼び出しできる
- エントリポイントと、呼び出しの引数を設定できる
- 使用感はデプロイと同じ
- 演習
-
コントラクトを呼び出し、結果を確認する
SmartPy: 条件分岐
https://smartpy.io/docs/general/control_statements/
SmartPy のふしぎポイント。
Python の if
文は使えない。 sp.if
メソッドを使う:
# x == 10 なら result を 0 に、それ以外は result に足す
sp.if x == 10 :
self.data.result = 0
sp.else:
self.data.result += x
for
, while
についても sp.for
,
sp.while
を使う。
SmartPy: エラー終了
https://smartpy.io/docs/general/raising_exceptions/
コントラクト実行をエラーで終了したい時は
sp.failwith("メッセージ")
sp.if self.data.x > 10:
sp.failwith("Error: x is bigger than 10")
sp.failwith
するとコントラクト実行は失敗する:
- 実行を引き起こしたトランザクション自体も失敗する
- 全ての状態はトランザクション前のまま。元に戻す心配はしなくて良い
SmartPy: コントラクトからの送金
https://smartpy.io/docs/types/operations/
sp.transfer(引数, トークン量, 送金先)
例: コントラクトの残高を全て送金する:
def send_all(self):
adrs = sp.address("tz1aTgF2c3vyrk2Mko1yzkJQGAnqUeDapxxm")
sp.send(adrs, sp.balance)
SmartPy: 基礎データ型
https://smartpy.io/docs/
int
or nat
-
1
, 2
, 3
- 型を明示したい場合は
sp.nat(4)
,
sp.int(-10)
string
-
"hello"
bytes
-
sp.bytes("0x1234")
# Hex で表記
bool
-
True
, False
SmartPy: クリプト関連データ型
クリプト関連データ型
- アドレス
-
sp.address("tz1aTgF2c3vyrk2Mko1yzkJQGAnqUeDapxxm")
トークン量(tez) :
sp.tez(1)
: 1 tez
sp.mutez(1)
: 0.000001 tez
SmartPy: タプル
Tuple でも Pair と呼ぶ:
aPair = (1, "A String", True)
Tuple の分解
SmartPy: リスト
Array では…ありません。
aList = [3, 2, 1]
# aList = [4, 3, 2, 1]
aList.push(4)
# [5, 4, 3, 2, 1]。 aList は変わらない
bList = sp.cons(5, aList)
リストの分解
with sp.match_cons(aList) as x:
# aList が空でない場合
# x.head と x.tail が使える
...
sp.else:
# aList が空の場合
...
SmartPy: option
あるか、ないか。
# 無
aOption = sp.none
# 有
aOption2 = sp.some(10)
Option の分解
aOption.is_some() # 有だと True
aOption.open_some() # 有の時の中身
SmartPy: Map と Big_map
Key value pairs です。
Map は:
aMap = {"a" : 3, "b" : 5}
aMap["a"]
Big_map は作成時に sp.big_map(...)
を明示します:
aBigMap = sp.big_map({3 : True, 4 : False})
aBigMap[3]
違い
- Map は早いですが全体をロードするので巨大な KVS には向きません
- Big_map は遅いですがロード費用が安いので DB 的な KVS に使えます
SmartPy: レコード
レコードの生成
aRecord = sp.record(field1 = 1, field2 = "A string")
フィールドアクセス
SmartPy: バリアント
タグ付きユニオンのようなもの
aVariant = sp.variant("name", 1)
# 複数の値を持たせたければ Pair を使う
aVariant2 = sp.variant("name2", ("hello", True))
# 0引数の場合は () を使う
aVariant0 = sp.variant("name0", ())
SmartPy: バリアント #2
バリアントの分解
# v = sp.variant("foo", _) の場合 True
v.is_variant("foo")
# v = sp.variant("foo", x) の場合 x それ以外はエラー
v.open_variant("foo")
# バリアントによる条件分岐
with v.match_cases() as arg:
with arg.match("foo") as a1: # foo の場合
# foo の引数が a1 に
...
with arg.match("bar") as a2: #bar の場合
# bar の引数が a2 に
...
SmartPy: この機能がないと思ったら
リストのn番目が欲しい…
ありません!
Tezos の Michelson VM はスマートコントラクトの安全性のために array
などのヤバい機能は入っていません。
SmartPy も普通の Python
だと思っていると、いろんなものがありません。あまり、無いものを探し回らないように。
オフチェーン・プログラミング
主なオフチェーンプログラムの仕事:
- エンドユーザーのため UI
- Tezosノード RPC や indexer API
を使いブロックチェーン状態を調べる
- ウォレットと通信し、トランザクションの署名をしてもらう
- Tezos ノードに署名済みトランザクションを送付する
RPC
などの仕様を読めば全部自分で書けます。が、面倒なのでライブラリを使います
はじめての Taquito
Template を使ってみる:
$ git clone \
https://github.com/ecadlabs/taquito-react-template
$ cd taquito-react-boilerplate
$ npm run start
http://localhost:3000
にアクセス
- 演習
-
Taquito を試してみる
Taquito の使い方
taquito-react-boilerplate を少しづつ改造、をお勧めします
// 初期化
const tezos = new TezosToolkit("https://ghost.smartpy.io");
// ウォレットを通してコントラクトにアクセス
const contract = await tezos.wallet.at(contractAddress);
// ストレージの読み出し console.log() するとよい
const storage : any = await contract.storage()
// エントリポイントの呼び出し
await contract.methods.mymethod(引数).send();
SmartPy と Taquito での値の交換
SmartPy(Python風DSL) と Taquito(TypeScriptライブラリ) は
Michelsonデータの表現が違います
SmartPy ⇔ Michelson ⇔ TypeScript
読み替えが必要です。SmartPy IDEのMichelson
コードのstorageとparameterの型宣言が参考になるでしょう:
- int/nat => BigNumber
- list => array
- pair, record, map => オブジェクト
- big_map => big_map ID
- 諸々 => string
Tileverse
https://dailambda.jp/strass/tileverse/index.html
Tileverse は Tezos を利用した探索ゲームのようなものです。
- 一歩ごとにチェーンと通信を行なっていられないので、ある程度遊んだら動きをチェーンに登録します
- レアアイテムを見つけた!などの動きを偽造して登録できないように、ゲームのフロントエンドとスマートコントラクトで同じゲームロジックを実装してあり、動きの検証を行います
- リロードしてサイコロを振り直すのはチートではありません。(RNGは固定なので、同じ世界で同じ動きをすると同じ結果が出てきます)
- プレイ中に自分の周囲が他人に書き換えられ、動きが更新された世界では正しくなることがあります。その場合は新しい世界でやり直しとなります。
Tileverse #2
マップ
- 世界は無限の岩が広がっている。掘った場所しかデータとして保存しない
- 掘った部分の記憶域料金は掘った人が払う。0.0033tez/堀
- 移動はほとんど料金がかからない
- 16x16 の
map
を一単位 “chunk” にして、
Chunk
を無限に二次元 matrix の big_map
に足していく
- フロントエンドは chunk 単位でまとめて最大 256 タイル読み出す
- 1タイルごとに Tezos
から読み出すと、恐ろしい量の通信を行うことになる
Tileverse #3
フロントエンド
- プレーヤーの周りの chunk をスマコンの big_map から読み出し表示
- ゲームを提供しつつ、動きを記録
- Enter
が押されたら、動きをフロントエンド側で検証したのち、ウォレットを通してブロックチェーンに送る
スマートコントラクト
- 送られてきた動きを検証しつつ、マップデータを更新
- 検証が成功すれば正常終了し、マップデータは更新されたものに置き換わります
- 検証が失敗すればエラーになり、マップデータは元の値のまま
Tileverse #4
技術と料金の確認しただけなので、ゲーム性はない
これをどう面白くするかが結局重要
- グラフィックスを良くしてより RPG っぽくする
- アイテムに何か機能を持たせる (運が良くなる、など)
- 特定プレイヤー以外は変更できないような領域を提供する
- 他のプレーヤーを表示する、敵のようなものを導入する
- アイテムを NFT として流通できるようにする
- 料金を取ってお金持ちになる(笑