Tezos DApps ハッカソン
  DaiLambda, Inc.
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

https://www.8bidou.com/

ドット絵に特化した Tezos アート NFT
アートデータもオンチェーンに。

日本の方が作っています

デジタルアート NFT ?

常にある疑問
誰でもアクセスできるデータの hash を<所有>することの意味

🤔 投げ銭以上の意味あんのかな?

これからは、hash の所有者に +α の価値を提供できるか

Tezos トークン規格 FA2

FT, NFT 併せたインターフェースを定義。

FA2 準拠 FT/NFT は対応ウォレット+交換所+DApps で扱える

Tezos DApps: DEX

分散取引所

仮想通貨同士の交換を提供する

  • QuipuSwap
  • Plenty
  • SpicySwap
  • etc

Tezos DApps: Name service

名前を売るサービス

Tezos Domains https://tezos.domains/en

短い名前を占有、Tezos アカウントなどの情報と紐付けできる:

例: https://app.tezos.domains/domain/dailambda.tez

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: Browser IDE

https://smartpy.io/ide

ここでスマートコントラクトを:

  • 書き
  • テストし
  • デプロイできる

簡単な 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 という名前でライブラリを使用:

import smartpy as 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   # 二倍

より一般的には:

  self.data.ストレージ変数名
❗️️️️
.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)
    • 間違って Mainnet 選ぶとお金を取られる
  • 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/general/block_properties/

コントラクトが実行された際の様々な値を sp.変数名 でアクセスできる。

  • sp.amount: 総金額
  • sp.balance: コントラクトが持つ残高
  • sp.source: トランザクションを引き起こしたアカウント
  • sp.sender: コントラクトを直接呼び出したアカウント
  • etc

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 の分解

x1, x2, x3 = aPair

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

フィールドアクセス

aRecord.field2

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 によるオフチェーンプログラミング

https://tezostaquito.io/

TypeScript による Tezos オフチェーンプログラミング。

私こんな言語知りませんよ

はじめての 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 として流通できるようにする
  • 料金を取ってお金持ちになる(笑