Skip to main content

実践・白紙練習:ゼロから手打ち

Counter 改造10本ノック:ledger と circuit を作り変える

第一弾でコンパイルした Counter を、少しずつ作り変えて10本ノック。増やす数を変える・引数で増やす(disclose の壁)・減らす・読む・上限の門番・2つの数字…と、ledger と circuit の感覚を手で固める。すべて compact compile で実際に通る/わざと失敗する例つき。


Counter 白紙練習(第一弾) で、白紙から Counter を書いてコンパイルできました。ここからは 改造10本ノック。同じ Counter を、少しずつ作り変えて、ledgercircuit の感覚を手で固めます

このページが嬉しいのは、Docker もウォレットも要らないこと。使う道具は第一弾と同じ compact compile だけ。だから、何度でも気軽に回せます。デプロイ(第二弾)の前でも、合間でも、いつでもどうぞ。

今日の合言葉:

今日は 「契約」を作り変えて、「コンパイル」で確かめるノックです。

このノックの回し方

おすすめは、ノックごとに新しい練習場を作ること。第一弾と同じ手です。

各ノックは、こう進めます。

土台になる第一弾のゴール(これを毎回いじります):

合言葉の分解も、もう一度。


ノック1:2ずつ増やす

お題:押すたびに、round2 ずつ増えるようにしてみよう。

コード白紙

increment を「2ずつ増える」に変えてみよう。

ここで分かることround.increment(...) の中の数字を変えれば、増える量が変わる。第一弾の Uint<64> + count = count + 1 では型エラーになったのに、Counter なら increment(2) がすんなり通る——これが「増やすための便利部品」の力です。


ノック2:好きな数だけ増やす(disclose の壁)

お題:呼ぶときに数を渡して、incrementBy(n)n だけ増やしたい。まずは素直に書いてみよう。

これをコンパイルすると…わざと失敗します

こわい見た目ですが、意味はシンプルです。

「見せてOK」と宣言するのが disclose(...) です。

コード白紙

incrementBy(n) を、コンパイルが通る形に直してみよう(ヒント:disclose)。

ここで分かることdisclose(...) は暗号化ではなく、「この値は公開してOK」とコンパイラに伝える印。Counter は秘密を使わない契約ですが、「外から来た値を公開 ledger に書く」だけでこの確認が出ます。詳しくは Compact の書きかたdisclose の節へ。


ノック3:減らす(decrement)

お題:増やすだけでなく、1 減らす decrement も足してみよう。

コード白紙

increment はそのままに、decrement を1つ足してみよう。

ここで分かることCounter には increment の相棒 decrement がいる。circuit を増やせば「操作ボタン」が増える。Compiling 2 circuits: と出て、ボタンが2つになったのが分かります。


ノック4:今の値を読む(read)

お題round の今の数を返すだけの current() を作ってみよう。

コード白紙

round の現在値を返す current() を作ってみよう(ヒント:read、戻り値の型)。

ここで分かることround.read() で Counter の中身(数)を取り出せる。これまで circuit の戻り値はずっと [](空)でしたが、今回は Uint<64> を返しています。(): [](): Uint<64> の違い=「何も返さない/値を返す」が見えてきます。

⚠️ TypeScript を知ってる人ほど引っかかる罠:[] は「配列」じゃない

TypeScript だと [] は「空の配列」ですよね。でも Compact の (): [][] は、空のタプル(empty tuple)という“型”です。配列を返せという命令ではありません。

Compact ではタプルを [A, B](2つ組)のように書きます。[]中身が0個のタプル=「意味のある値を返していない」=他の言語の void にあたります。だから「何も返さない」と読むわけです。

TypeScript でいうと、こう対応します。

見た目が TypeScript そっくりなので [] を「配列」と読みたくなりますが、戻り値型の位置に出てくる [] はタプル型で、しかも空だから“何も返さない”。ちなみに本当に並びを返したいときは、Compact では Vector<n, T>(長さ固定の並び)など別の型を使います。


ノック5:上限の門番(assert + read)

お題:第一弾で assert を試したのを覚えていますか。今回は Counter の今の値を読んで、10 未満のときだけ増やす「門番つき increment」を作ってみよう。

コード白紙

round.read() を門番に使って、10 未満のときだけ増やす increment にしてみよう。

ここで分かること:第一弾では、自力版(Uint<64>)に assert を足しても型エラーが消えませんでした。でも Counter + round.read() なら、門番つき increment がちゃんと通りますassert(条件, "メッセージ") の第2引数(メッセージ)も忘れずに。assert は「この条件のときだけ先へ進める門番」です。


ノック6:2つの数字(round と total)

お題:ledger を2つにしてみよう。round(今回ぶん)と total(ずっとの合計)を両方 1 増やす increment を作ってみよう。

コード白紙

ledger を round と total の2つにして、両方 1 増やす increment にしてみよう。

ここで分かることexport ledger は何個でも置ける。状態の箱を増やせば、契約が覚えられることが増える。circuit は1つでも、その中で複数の ledger を触れます。第一弾の contract-info.json を思い出すと、今回は ledger 欄に roundtotal の2つが並びます。

🤔 よくある疑問:roundtotal の1個1個って、UTXO なの?

いいえ。ledger は UTXO ではありません。 ここは Midnight を理解するうえで大事なので、はっきりさせておきます。

Midnight には別々の2つのしくみがあります。

しくみ モデル ここに住むもの
① コントラクトの状態 ledger(公開状態) アカウント型(1つの箱にまとめて保存) roundtotal などの export ledger
② お金(トークン) Zswap のコイン UTXO 型(Cardano の eUTXO に近い) tDUST などの残高・コイン

roundtotal の住人です。別々の UTXO に散らばっているのではなく、同じコントラクトの状態オブジェクトの中に、隣り合って入っている変数(欄)です。イメージはこう:

TypeScript でたとえるなら、roundtotal1つの state オブジェクトの中のプロパティ。別々の箱ではありません。

contract-info.jsonledger 欄に2つ並ぶのも、「この状態オブジェクトには round と total という欄がある」という設計図(スキーマ)を表しているだけ。UTXO の一覧ではありません。

では UTXO はどこ? → tDUST などのトークンを送受信するお金の世界(Zswap)で使われます。Cardano の eUTXO を受け継いだ部分です。increment() でカウンターを増やすのは ①の状態更新で、②の UTXO は動きません。

もっと知りたくなったら → 2.2 Account と UTXO(2つのモデルの違い)/2.4 デュアル台帳システム(トークンの二重構造)。


ノック7:reset を作ろうとして…(わざと失敗)

お題:「0 に戻す reset() がほしい」。Counter にもありそうですよね。書いてみましょう。

コンパイルすると…失敗します

ここで分かること:これは第一弾の count += 1operation += undefined ...)とそっくりの教訓です。型(ここでは Counter)が持っている操作しか使えない。何が使えるかは、暗記ではなく Compact の書きかた や公式リファレンスで都度確認するのが正解。

どうしても「0 に戻したい」なら、Counter ではなく自分で値を管理する別の設計(Cell などの ledger 型)が必要になります。今は「Counter には reset が無い」と分かれば十分です。


ノック8:好きな数だけ減らす(decrementBy)

お題:ノック2の応用。引数で渡した n だけ減らす decrementBy(n) を作ってみよう。ノック2で学んだ「壁」を思い出して。

コード白紙

incrementBy(n) と decrementBy(n) を、両方コンパイルが通る形で作ってみよう。

ここで分かることdecrement でも、引数を公開 ledger に渡すなら disclose(...) が要る。ノック2の「壁」は increment 専用ではなく、「外から来た値を公開 ledger に書く」全部に共通だと分かります。


ノック9:全部のせ(combined)

お題:ここまでのノックを1つの契約にまとめてみよう。roundtotal を持ち、門番つき incrementincrementBy(n)decrementcurrent() を全部入れる。

コード白紙

round + total を持ち、increment(門番つき)/ incrementBy / decrement / current を全部入れた契約を書いてみよう。

ここで分かることCompiling 4 circuits: = ボタンが4つ。contract-info.jsoncircuits 欄には increment incrementBy decrement currentledger 欄には round total が並びます。自分で書いた契約の形が、そのまま生成物の形になるのが、はっきり見えます。


ノック10:自分のお題(決まった答えなし)

お題:最後は、自分でお題を決めて作ってみよう。たとえば——

  • 押すと round は +1、でも total は +2 になる
  • current() に加えて「合計を返す」currentTotal() も作る
  • 上限を 100 から 1000 に変えて、メッセージも変える
  • incrementBy(n)n の型を Uint<16> から Uint<8> に変えてみる(何が起きる?)
白紙練習

自分で決めたお題と、それを作って compile したときに分かったこと(成功でも失敗でも)を書いてみよう。

ここで分かること:お題を自分で作れるようになったら、もう「ゼロから何を作ればいいか分からない」状態は卒業です。小さな改造を白紙から何度も——それが、Battleship のような複雑なアプリを読める力に、まっすぐつながります。


✍️ 仕上げの白紙練習

クイズ

Counter が持っている主な操作を3つ挙げてみよう。reset はある?

クイズ

circuit の引数を公開 ledger に書こうとすると disclose が要るのはなぜ?

クイズ

ledger(round や total)って、1個1個が UTXO なの?

クイズ

(): [] と (): Uint<64> のちがいは? [] は配列?

今日の呪文

今日の勝利条件

今日のまとめ

  • Counter の操作は increment / decrement / readreset は無い——型が持つ操作しか使えない
  • circuit の引数を公開 ledger に書くなら disclose(...)
  • ledger は何個でも、circuit も何個でも増やせる。書いた形が、そのまま生成物の形になる。
  • 道具は compact compile だけ。だから何度でも反復できる。エラーが出る改造も、立派な練習。

つぎに読むページ