slides4
ブロックチェーンの数理と実装


第4回/全5回


DaiLambda, Inc.
Jun FURUSE/古瀨 淳
応用数理I,社会数理概論I, Nagoya university, 2023-04-28

ブロックチェーンでどんな DApps を作るか

何でもいいです、できたら:

  • 普通の中央集権 DB ではできない事
  • 暗号通貨でのやりとり (本気でやると税とか大変だけど
  • オープンになっていることの魅力
  • ブロックチェーンやネットの外の世界と繋がっていると面白い

今回はほぼ Tezos の話です

なのでマークつけません

Tezos ブロックチェーンの基本

  • 普通の DB と比べれば遅い: 更新単位時間 15秒
  • 記憶域は高い: 1byte = 0.04円
  • 各アカウントはトークンを保持しており、使用料はそこから支払われる
    • 送金手数料、記憶域確保手数料
  • 公開鍵暗号によるアカウント認証
    • 秘密鍵を知っている人がオーナー
    • 公開鍵はチェーン上に公開 (Reveal操作)
    • アカウント操作が正当であることを公開鍵で検証
  • スマートコントラクト: 特殊なアカウント。KT1...
    • トークン保有量の他に、コントラクトコード、データを持つ
    • 送金をトリガーにコントラクトコードを実行

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 開発

  • ウォレット (Kukai, Temple など)
  • テストネット
  • ブロックエクスプローラ
  • Tezos スマートコントラクトの構造
  • スマートコントラクト記述とデプロイ (SmartPyなど)
  • オフチェーンプログラミング (Taquitoなど)

ドキュメント

いろいろありますが、公式のものが一番良い:

今日扱うツール関連

全て英語です。キツイ人は自動翻訳使ってください。

ウォレットを変えます

すいません、Kukai wallet は使いにくすぎるので、

Temple ウォレット をインストールしてください

演習
Temple ウォレットのインストール

Temple ウォレットのインストール

Safari は使えません。 Chrome か Firefox を使ってください。

  1. https://templewallet.com/download?platform=extension
  2. 使っているブラウザの INSTALL を押す。
  3. 拡張機能をインストール。

私の授業の課題が終わったらこの拡張を消して構いません。

Temple ウォレットのインストール

Safari は使えません。 Chrome か Firefox を使ってください。

  1. https://templewallet.com/download?platform=extension
  2. 使っているブラウザの INSTALL を押す
  3. 拡張機能をインストール

Temple ウォレットの初期化

前回作ったアカウントを復活させる

  1. Import existing Wallet

Temple ウォレットの初期化

  1. 前回作ったアカウントのシードフレーズを入力
    全部をコピーして、#1 にペーストすると一度に埋めてくれる
  2. Next

Temple ウォレットの初期化

  1. Password を入力(2回)
  2. Accept terms にチェック。 Skip Onboarding にチェック。
  3. Import

同じアカウントが回復できるはず

Temple: Ghostnet を選ぶ

Mainnet での残高は 0 tez。

Ghostnet に切り替えれば残高が見えるはず。

Temple: サブアカウントを作る

サブアカウントも復活できる

  1. 🤖を押す
  2. \(\oplus\) Create or Restore account
  3. “Account 2” を Create Or Restore Account

Temple: 送金練習

: Account 1 から Account 2 にトークンを送ってみる

Tezos のスマートコントラクト

Michelson VM というスタックマシンを使用

Michelson は人間には辛いので高級言語を使う

Tezos のスマートコントラクト

ストレージ
記憶領域。値を一つ取ります。
Map や Big_map を使うと巨大な連想配列データを保存できますが、
記憶域料金がかかります。これが Tezos での一番のコストになります。
エントリポイント
複数のプログラム実行エントリポイントを持てます。
外部から機能の呼び分けが可能です。
スマートコントラクトの動作

エントリポイントは送金命令からパラメータを受け取り、
そのコードを実行し、

  • ストレージの値の更新
  • 必要であれば、さらなる送金命令の発行

を行います

SmartPy

https://smartpy.dev

Python で Tezos スマートコントラクトを書くと、
Michelson にコンパイルしてくれる。

私は Python 書いたことありません

SmartPy: Browser IDE

https://legacy.smartpy.io/ide

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

  • 書き
  • テストし
  • ブロックチェーンにデプロイできる

SmartPy IDE の使い方

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

このコードを IDE にコピーペーストしてください

SmartPy のコンパイル

▶︎ を押すと Python コードが Michelson にコンパイルされます

ここでエラーがでたら直して再挑戦

コンパイルされた Michelson コード

Deploy Michelson Contract: コンパイル結果を見る

Code: コード
parameter (int %add);
storage   int;
code
  {
    UNPAIR;     # @parameter : @storage
    # == add ==
    # self.data.x += params # @parameter : @storage
    ADD;        # int
    NIL operation; # list operation : int
    PAIR;       # pair (list operation) int
  };
Storage: ストレージ
1

スマートコントラクトのデプロイ

コンパイルされたコードをブロックチェーンへアップロード

Deploy Michelson Contract > Deploy Michelson Contract

デプロイ画面

  1. Ghostnet を選ぶ
  2. SELECT ACCOUNT
    1. Temple を選択
    2. パスワードを聞かれるかも
    3. Account 1 を選ぶ
  3. VALIDATE
  4. 必ず ESTIMATE COST FROM RPC を押す
    これを忘れるとデプロイに成功したようで、しません。
  5. DEPLOY CONTRACT
  6. ACCEPT
  7. Temple が確認してくるので Sign する

デプロイの成功

ちゃんとデプロイが成功すれば、ブロックチェーンの進捗に従って Block Confirmations が育つはず

CONTRACT ORIGINATED SUCCESSFULLY! と出たのに confirmation が育たない場合
ESTIMATE COST FROM RPC の押し忘れ。やり直してください

デプロイされた様子を見る

  1. OPEN EXPLORER

現在のストレージの状態 (1) が見れます

スマートコントラクトを呼び出してみる

  1. INTERACT

  1. スマートコントラクトに送るパラメーターを設定
  • エントリポイント名 (Add)
  • パラメータ (例えば 42)

スマートコントラクトを呼び出してみる

  1. Build Transaction Parameters
  2. SELECT ACCOUNT
  3. Temple を選んでアカウントを選ぶ
    • Amount in tez: 同時に送るトークン量を決める: 今回は 0 tez で構わない
    • Fee, Gas, Storage: いじらなくてOk
  4. CALL CONTRACT

✅が出て Block Confirmation が進めば成功

呼び出し結果を見る

Call result

実行の結果、ストレージが 1 + 42 = 43 になっている:

...                
"operation_result": {
    "status": "applied",
    "storage": {
        "int": "43"
    },
...

SmartPy プログラムの書き方

簡単な SmartPy コード: import

sp という名前でライブラリを使用:

import smartpy as sp

必ず頭に書いてください。おまじないだと思っていいです。

他のライブラリの import はできません

Python の文法が使える DSL (ドメイン特化言語) であって、
Python そのものではない

SmartPy: クラス

コントラクトは sp.Contract のサブクラスとして定義:

class MyContract(sp.Contract):
  ...

これもおまじないだと思って構いません

SmartPy: ストレージ初期化

__init__ メソッドでデプロイ時のストレージの初期化を記述

def __init__(self, initx):
  self.init(x = initx)

デプロイ時に initx を受け取り、ストレージ変数 xinitx にセットする

メソッド定義の第一引数は常に self です。ここからクラス内部のメソッドやデータにアクセスできる

SmartPy: ストレージ初期化

一般的には:

def __init__(self, 引数1, 引数2, ...):
  self.init(ストレージ変数1 =1, 
            ストレージ変数2 =2, 
            ...)

__init__ では self.init(..) を呼び出してストレージ初期化する以外のことはできません

Python なので字下げに注意

Python のプログラムの意味はインデントに影響されます。

正しいインデントを!

import smartpy as sp

class MyContract(sp.Contract):
def __init__(self, initx):  # ← 字下げ忘れ
  self.init(x = initx)
  
  ...

Error: expected an indented block

SmartPy: エントリポイント

エントリポイントは @sp.entry_point アトリビュート付きのメソッドとして書く:

  # ストレージ x の値を増す
  @sp.entry_point         # エントリポイントだよ
  def add(self, addx):
    self.data.x += addx

self.data.x += addx は左辺の変数への加算。

    self.data.x = self.data.x + addx

でもいいよ

SmartPy: エントリポイント

一般的にはエントリポイントの宣言は:

  @sp.entry_point
  def メソッド名(self, 引数1, 引数2, ...):
    ... コード ...

デフォルトエントリポイント名は default。特に指定せずアカウントに送金するとこのエントリポイントが呼ばれる

❗️️️
@sp.entry_point を忘れがち。忘れると、
Error: New command outside of contract などと言われる

SmartPy: ストレージ変数へのアクセス

self.data.ストレージ変数名 でアクセスする:

    self.data.x += addx             # 加算
    self.data.x = self.data.x * 2   # 二倍

SmartPy: ストレージ変数へのアクセス

一般的にはストレージ変数へはアクセスは:

  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: テスト結果

▶︎ を押すとコードをチェック、問題がなければテストを行う。

右側にテストの実行結果が表示される。

  • デプロイされると初期ストレージは x = 1
  • add エントリポイントを 2 のパラメータで呼び出すと x = 3 になる

ことがわかる

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 でエントリポイントを呼び出しできる

  • エントリポイントと、呼び出しの引数を設定できる
  • 使用感はデプロイと同じ

演習: 簡単な計算機を作る

add 以外に sub, mul, div エントリポイントを作って、 それぞれで、現在のストレージの値に対し減算、乗算、除算を行う

除算は整数。(SmartPy では / と書いても // になる。)

0除算

テスト時に 0除算があると、IDE ではエラーを検出してくれる。

ブロックチェーンにデプロイし、0除算を行う送金をすると?
→ 送金命令として不正なのでブロックに取り込まれない

Failsafe な設計

Tezos では何かエラーが起これば送金全体が失敗し、なかったことになる。

Failsafety

Tezos では何かエラーが起これば送金全体が失敗する

  @sp.entry_point
  def fail_if_0(self, y):
    self.data.x = 42
    self.data.x = self.data.x / y # 0 だと失敗する

もし fail_if_0(y) を呼び出すと 0除算で失敗するが、
ストレージ x42 にはならず、元の値のまま

他のブロックチェーンでは、実行失敗時に、それまでの変更がストレージに残ってしまうものがある:

  • どこで失敗するかの把握は難しい (例えば 0除算)
  • 気付かぬうちに不変状態が破れた状態になると誤動作、脆弱性の元

演習: 複数のストレージ変数

2つのストレージ変数を持つ投票コントラクトを作ろう

  • xy の二つのストレージ変数。初期値はそれぞれ 0
  • vote_xvote_y の二つのエントリ
    • 各々、xy に 1 足す

Python に ++ はありません。(私は、知らなかった💦)

まあ、認証もないし、投票と呼ぶのはおこがましい

演習: 複数引数の __init__ とエントリポイント

簡単だけど、

  • xy の二つのストレージ変数
    • デプロイ時にそれぞれの値を指定できる
  • set_x_and_y エントリ
    • 2つの整数を受け取り、xy をその値に変更

テスト呼び出しに注意

引数名の指定が必要:

  scenario += contract.update(newx=4, newy=3)

contract.update(4, 3) は怒られる

SmartPy: 関数定義

繰り返す操作は関数として定義しよう。

@sp.entry_point のないメソッド定義

  # @sp.entry_point はいらない
  def func(self, arg1, arg2, ..):
    ...
    return e  # 何か呼び出し元に値を返したい場合

呼び出し

  res = self.func(exp1, exp2, ..)


❗️ self. をすぐ忘れる

SmartPy: 条件分岐

https://smartpy.dev/docs/general/control_statements

SmartPy のふしぎポイント。

Python の if 文は使えない。 sp.if メソッドを使う:

# 偶奇で分岐
sp.if x % 2 = 0:     # % は剰余
    self.data.result += 1
sp.else:             # if, else の後の : を忘れがち
    self.data.result += -1

❗️ sp.ifsp.elsesp. は書くのをとかく忘れがち

SmartPy: エラー終了

https://smartpy.dev/docs/general/raising_exceptions

コントラクト実行をエラーで終了したい時は
sp.failwith("メッセージ")

sp.if self.data.x >= 10:
    sp.failwith("Error: x must be less than 10")

sp.failwith するとコントラクト実行は失敗する:

  • 実行を引き起こしたトランザクション自体も失敗する
  • 全ての状態はトランザクション前のまま。元に戻す心配はしなくて良い

SmartPy: 基礎データ型

(ざっくりいきます。必要になったら見返してください)

SmartPy: 基礎データ型

https://smartpy.dev/docs/general/types#basic-types

int or nat
1, 2, 3
string
"hello"
bytes
sp.bytes("0x1234") # Hex で表記
bool
True, False
unit
Unit しか値を取らない型

SmartPy: intnat

https://smartpy.dev/docs/general/types#nat

ほとんど型が出てこないが SmartPy は型付きの言語。(Michelson も型付きなので)

\(\mathbb{Z}\)の型 int\(\mathbb{N}\) の型 nat がある

ほとんどの場合、自動判別されるが明示することも可能:

  • sp.int(-3), sp.nat(42)sp.nat(-10) はエラー
  • 変数に対しては sp.set_Type(変数, 型) で宣言:
  def f(x, y):
    sp.set_type(x, TInt)  # x は int
    sp.set_type(y, TNat)  # y は nat

intnat は別の型

https://smartpy.dev/docs/general/types#nat

  sp.int(10) + sp.nat(20)  # エラー

明示的に型変換する必要がある:

  sp.int(10) + sp.to_int(sp.nat(20))

int から nat は失敗する可能性がある:

  sp.as_nat(int(10))

nat - natnat ではなく int !!!

  sp.nat(12) - sp.nat(10) == sp.int(2)
  sp.nat(10) - sp.nat(12) == sp.int(-2)

SmartPy: 文字列 string

   x = "Hello World"
   # 連結
   y = "Hello" + "World"
   # 文字列リストの連結
   sp.concat([ "Hello", " ", "World" ])
   # 長さ。nat です
   sp.len("Hello") == sp.nat(5) 
   # サブストリング。成功すると Some サブストリング を返す
   sp.slice("Hello", 1, 3) == sp.some("ell")
   # サブストリング。範囲外になると None を返す
   sp.slice("Hello", 3, 7) == sp.none

ASCII だけ。日本語は使えません。 日本語はオフチェーンで扱う

SmartPy: バイトデータ bytes

  x = sp.bytes("0x2345")
  # 連結
  x = sp.bytes("0x1234") + sp.bytes("0x5678")
  # バイトリストの連結
  x = sp.concat([ sp.bytes("0x12"), sp.bytes("0x34"),
                  sp.bytes("0x56") ])
  # 長さ。結果は nat です
  sp.if sp.len(sp.bytes("0x1234")) == 2:
  # サブバイト
  sp.slice(sp.bytes("0x12345678"), 1, 2)
    == sp.some( sp.bytes("0x3456"))
  sp.if sp.slice(sp.bytes("0x12345678"), 3, 7) == sp.none

SmartPy: Booleans

  • True, False
  • ~True == False
  • (True | False) == True
  • (True & False) == False

SmartPy: コンテナ型

(きりがないです。必要になったら見返してください)

SmartPy: タプル

https://smartpy.dev/docs/types/pairs

Tuple でも Pair と呼ぶ

aPair = (1, "A String", True)

Tuple の分解

(x1, x2, x3) = aPair

SmartPy: リスト

https://smartpy.dev/docs/types/lists

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

https://smartpy.dev/docs/types/options

あるか、ないか。

# 無
aOption = sp.none 

# 有
aOption2 = sp.some(10)

Option の分解

aOption.is_some() # 有だと True

aOption.open_some() # 有の時の中身

SmartPy: 連想配列: Map と Big_map

https://smartpy.dev/docs/types/maps_and_big_maps

Map は:

aMap = {"a" : 3, "b" : 5}
aMap.get("a", default_value= 0)

Big_map は作成時に sp.big_map(...) を明示します:

aBigMap = sp.big_map({3 : True, 4 : False})
aBigMap.get(3, default_value= 0)

違い

  • Map は早いですが巨大な連想配列には向きません
  • Big_map は遅いですがロード費用が安いのでデータベース用の連想配列です

SmartPy: 連想配列: Map と Big_map

https://smartpy.dev/docs/types/maps_and_big_maps

初期化

aMap = {"a" : 3, "b" : 5}
aBigMap = sp.big_map({3 : True, 4 : False})

読み出し

m.get(key, default_value=見つからなかった時の値)

更新

m[key] = value

削除

del m[key]

SmartPy: レコード

https://smartpy.dev/docs/types/records

レコードの生成

aRecord = sp.record(field1 = 1, field2 = "A string")

フィールドアクセス

aRecord.field2

SmartPy: バリアント

https://smartpy.dev/docs/types/variants

タグ付きユニオンのようなもの

aVariant = sp.variant("name", 1)

# 複数の値を持たせたければ Pair を使う
aVariant2 = sp.variant("name2", ("hello", True))

# 0引数の場合は () を使う
aVariant0 = sp.variant("name0", ())

SmartPy: バリアント #2

https://smartpy.dev/docs/types/variants

バリアントの分解

# 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: グローバルプロパティ

https://smartpy.dev/docs/types/mutez#global-properties

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

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

SmartPy: アドレス

sp.address("tz1aTgF2c3vyrk2Mko1yzkJQGAnqUeDapxxm")

演習: 認証をしてみる

送金命令を出した人(sp.source)が設定した人でなければ
カウンタを増やせない

sp.if sp.source != sp.address("tz1.."):
  sp.failwith("permission denied")

テスト時に source を指定

エントリポイント呼び出しの後に .run(source = アドレス)

def test():
  myself = sp.address("tz1....") # あなたのアドレス
  alice = sp.test_account("Alice")  # ダミーアカウント
  scenario = sp.test_scenario()
  contract = MyContract()
  scenario += contract
  scenario += contract.default().run(source = myself)
  scenario += contract.default().run(source = alice.address)

SmartPy: トークン量(tez)

  • sp.tez(1) : 1 tez
  • sp.mutez(1) : 0.000001 tez

mutez (= μtez, \(10^{-6}\) tez) が最小単位。整数として取り扱う。

SmartPy: コントラクトからの送金

https://smartpy.dev/docs/types/contracts_and_addresses#transfer

送金
sp.send(送金先, トークン量)
スマートコントラクト呼び出し
sp.transfer(引数, トークン量, 送金先)

例: コントラクトの残高を全て送金する:

@sp.entry_point
def send_all(self):
  adrs = sp.address("tz1aTgF2c3vyrk2Mko1yzkJQGAnqUeDapxxm")
  sp.send(adrs, sp.balance)

演習: ブーメラン

送金されたら、同じ額を source に返す

  sp.send(sp.source, sp.amount)

演習: クラウドファンディング(ブラックホール)

呼び出した人とその総送金額を big_map に記録します。

初期化

# 空の big_map を作る
self.init(table = sp.big_map())

送金があったとき

# 記録がない人の寄付額は0
already_donated = \
  self.data.table.get(sp.source, 
                      default_value = sp.tez(0))
# 新寄付額で更新
self.data.table[sp.source] = \
  already_donated + sp.amount

演習: クラウドファンディング #2

先ほどのクラウドファンディング(ブラックホール)、このままではせっかく寄付してくれたトークンを引き出すことができません!

認証を施して、自分がアクセスしたときだけ、溜まったトークンを自分に全額送金するようにしてください。

演習: 銀行

先ほどのクラウドファンディング(ブラックホール)を改造して
銀行を作れます。

withdraw エントリポイントを作って、各ユーザーが指定した額を引き出せるようにしてください。

  • source の残高以上の額を引き出せてはいけない
  • 引き出したら残高を更新しなければならない

演習: 俺俺コイン

クラウドファンディングや銀行では tez を扱いましたが、 tez の代わりに nat を扱うようにすると fungible token (のようなもの) が作れます。

mint(amount)
amount量の俺コインを呼び出した人に与えます。管理者しか実行できません
transfer(dest, amount)
amount量の俺コインを source の残高から引き、それを dest で指定したアドレスの残高に足します。

SmartPy: この機能がないと思ったら

リストのn番目が欲しい…

ありません!

Tezos の Michelson VM はスマートコントラクトの安全性のために array などのヤバい機能は入っていません。

SmartPy も普通の Python だと思っていると、いろんなものがありません。あまり、無いものを探し回らないように。

何か探すときは https://smartpy.dev/docs/manual/introduction/overview の左側からか 🔍Search を使ってください。

オフチェーンプログラミング

本来は、オンチェーンのスマートコントラクトを作るだけでなく、 そこにアクセスしやすくデザインしたオフチェーンのソフトウェアも必要です:

  • ウォレット連携
  • ブロックチェーンからの情報取得
  • 送信するデータ生成
  • GUI


オフチェーンプログラムに比べると、スマートコントラクトは短めです。 ただし、金銭を扱う公開ソフトウェアとなるので、
バグがあると大変です。 (次回話します)

課題

次のいずれかを SmartPy で実装し、Ghostnet にデプロイしてください:

  1. クラウドファンディング#2 (1点)
  2. 銀行 (2点)
  3. 俺俺コイン (3点)
  4. 上の三つより何か面白いもの (1~6点)

提出物:

  • 何を実装したか。4 の場合は何を作ったのか説明をください
  • デプロイした KT1* アドレス