Skip to main content

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

Counter 白紙練習:ledger / circuit / compile を体で覚える

白紙のディレクトリから Counter 契約を手打ちし、わざとエラーを出して直し、compact compile を成功させるまでを反復練習する実践ページ。「契約・コンパイル・デプロイ・呼び出し」を頭ではなく手で覚えます。


このページは、白紙のディレクトリから自分の手で Counter を打つための実践ノートです。 テンプレート生成で一気に作るのではなく、counter.compact を1文字ずつ書き → わざとエラーを出し → 直し → コンパイル成功まで、ゆっくり一緒に進みます。

ここ、かなり大事です。何度でも繰り返してOKです。むしろ、繰り返すために作ったページです。counter-01 がうまくいったら、次は counter-02counter-03 …と新しい練習場を作って、同じ流れを手で再生してください。

はじめに:なぜ Counter から始めるのか

最初から Battleship のような複雑なアプリを読むと、ファイルが多すぎて迷子になります。まずは Counter だけに集中します。

Counter は、

  • 数字がある
  • ボタンを押す
  • 数字が増える

という、いちばん小さなアプリです。この最小構造を通して、つぎの感覚を身体に入れます

まずは全部理解しようとしなくて大丈夫です。今日のゴールは「白紙から書いて、コンパイルが通る」ところまで。それだけで十分な前進です。

今日の合言葉

声に出すくらいの気持ちで、何度も唱えてください。

もう少し噛み砕くと:

順番でいうと:

そして、今日ずっと使う比喩はこれです。

ステップ1:作業用ディレクトリを作る

まずは練習場(空っぽのフォルダ)を作ります。

すでに練習用の場所がある人は、そこに counter-01 を作ってもOKです。

いまどこにいるか確認しましょう。

ls で何も出てこなければ大成功です。

理想のディレクトリ構造(いまの状態):

ステップ2:contract と counter.compact を作る

つぎに、スマートコントラクト本体を置くフォルダを作ります。

確認:

期待結果:

いまの構造:

ステップ3:自力版 Counter を書いてみる

いきなり「正解」を写すのではなく、まずは人間にいちばん分かりやすい自力版を書いてみます。counter.compact に、こう打ってみてください。

一行ずつ、人間の言葉に翻訳します。

export ledger count: Uint<64>; は——

export circuit increment(): [] は——

count = count + 1; は——

この段階のコードは、人間にはとても素直で分かりやすいですね。ここから先で、Compact が「人間の素直さ」と「機械の厳しさ」の間でどう振る舞うかを体験していきます。

つまずき1:UnitUint(typo と型の話)

練習中、実際にこういう打ち間違いがありました。

正しくは:

初学者向けにまとめると:

エラーは先生です。Unit と打ってしまっても落ち込まなくて大丈夫。「あ、Uint だった」と気づけたら、それが一歩前進です。

ステップ4:compact のバージョン確認

コンパイルの前に、道具のバージョンを見ておきます。

実際の例:

数字は環境で変わってOKです。「compact というコマンドが反応する」ことが確認できれば十分です。

ステップ5:compact compile の正しい形

最初、こう打ってみました。

すると、使い方(usage)が出ました。

ここから分かることは、Compact の compile には入力ファイル出力先ディレクトリの両方が必要だ、ということです。

正しい形:

意味:

これも比喩で言うと:

つまずき2:count = count + 1 で型エラー

さて、自力版(count = count + 1;)をコンパイルすると、こんなエラーが出ます。

落ち着いて読みます。

ここでの大事な学び:

スマートコントラクトは「お金や状態がチェーンに残る」世界です。だから「うっかり最大値を超えて壊れる」ことを、コンパイラがあらかじめ止めてくれます。最初は厳しく感じますが、これは守ってくれている厳しさです。

つまずき3:assert を門番にする

「最大値を超えないようにチェックすればいいのでは?」——その直感は正しいです。assert(アサート)を使ってみます。

最初はこう書きました。

でも Compact の assert にはメッセージが必要でした。正しくはこうです。

意味:

直感的に言うと:

つまりこのコードは——

人間語にすると:

…なのですが、それでもさっきの型エラーは消えませんでした。

人間の「だいじょうぶ」と、型システムの「型として保証されている」は別物なんですね。ここ、けっこう深い学びです。

つまずき4:count += 1 も試す

+= 1 なら?」と試したくなります。やってみましょう。

すると、別のエラーが出ます。

意味:

ここで、つぎの理解にたどり着きます。

もう少し正確に言うと:

比喩でいうと:

ステップ6:最小 Counter 契約の完成

ここまでの遠回りが、いちばんの近道でした。「増やしたいなら Counter を使う」——これがモデル回答です。

counter.compact を、こう書き直します。

一行ずつ分解します。

export ledger round: Counter; は——

さらに細かく:

round.increment(1); は——

ここを強調しておきます。

自力版と Counter版を並べると、ちがいが一目で分かります。

自力版(理解用):

Counter版(実務用):

Counter 型は標準ライブラリ(CompactStandardLibrary)が用意してくれている「増減の作法」を内側に持っています。だから「上限チェックをどうしよう」と人間が悩まなくても、安全に増やせる入口だけが渡されるわけです。

ステップ7:コンパイル成功

いよいよコンパイルします。

成功すると、こんな出力が出ます。

読み解くと——

Missing public parameters for k=5 ... は「証明に使う公開パラメータを初回だけダウンロードしている」だけなので、エラーではありません。びっくりしなくて大丈夫です。

ステップ8:output の中身を眺める

コンパイルが終わると、contract/ の中に output/ フォルダが自動でできています。VS Code で開くと、こんな感じです。

ここ、初学者にとってすごく大事なポイントです。

だれが何を作るのか、はっきりさせます。

各ファイルのざっくり説明:

ちなみに output/compiler/contract-info.json を開くと、コンパイラ自身のバージョンや、見つかった ledgercircuit が書いてあります(実際の中身の一部):

ledger の欄に roundstorage: Counter)が、circuits の欄に increment が、ちゃんと載っていますね。自分が書いた counter.compact が、機械の言葉に変換されたことの「証拠」です。

output は全部読まなくていい

もう一度、比喩で締めます。

index.jszkir の中身は、今は読まなくていいが、あとで必要になる部品です。デプロイのときに、この部品たちを使います。今は「眺めるだけ」でOKです。

ステップ9:pnpm で cli の土台を作る

ここからは「次の準備」です。生成された部品を使って、いずれチェーンへデプロイし、callTxincrement を呼びます。そのための CLI 用フォルダを作るところまでやります。

まず counter-01 の直下に戻ります。

いまの場所を確認:

CLI フォルダを作って初期化:

pnpm init のあと、package.json はこんな形になります。

つぎに、TypeScript を動かす道具を入れます。

実際の出力例:

こんな警告が出ることがあります。

ステップ10:deploy.ts で TypeScript 実行確認

最後に、TypeScript の CLI が動くかどうかだけ、小さく確かめます。本格的なデプロイ実装は次回にまわします。今日は「土台が動く」ことの確認です。

src フォルダと deploy.ts を作ります。

src/deploy.ts に、まずは1行だけ書きます。

実行:

期待出力:

これが出たら、今日の小ゴール達成です。TypeScript の CLI が、ちゃんとあなたのコードを動かしてくれました。

最終的なディレクトリ構造

ここまでの全体像です。これが今日作った「練習場ひとつぶん」の姿です。

✍️ 白紙練習(何度でも反復)

このページは反復用です。答えを開く前に、いったん自分の手から出してみてください。手で書くと「わかったつもり」がはがれます。counter-02, counter-03 …と練習場を増やして、何度でもどうぞ。

コード白紙

counter.compact を pragma から白紙で書いてみよう(Counter 版・コンパイルが通る形)。

白紙練習

compact compile の正しいコマンドの形と、その意味を自分の言葉で書いてみよう。

クイズ

ledger・circuit・callTx は、それぞれ何を表す?

クイズ

自力版で「count = count + 1」を書くと型エラーになるのはなぜ?

クイズ

Uint<64> と Counter のちがいは? なぜ increment には Counter を使う?

クイズ

assert の役割は? Compact の assert で忘れがちなことは?

クイズ

compact compile が生成する output には何が入っている? そのうち「自分で書く」のはどれ?

今日の呪文

今日の勝利条件

ひとつでもチェックがついたら、今日は前進です。全部ついたら大成功。

今日のまとめ

  • Counter は「数字を1ふやすだけ」の最小アプリ。でも ledgercircuitcompile の感覚が全部入っている。
  • 自力版(Uint<64> + count = count + 1)は人間には分かりやすいが、Compact の型チェックに引っかかる。
  • 増やしたいなら Counter 型round.increment(1) で、安全に「1増やす」を表現できる。
  • 自分で全部書く必要はない。書くのは小さな counter.compact だけ。compile すれば output が自動生成される。
  • エラーは先生。型エラーも assert のメッセージ忘れも、ぜんぶ「Compact の安全への厳しさ」を教えてくれている。

次回予告

今日できたこと:

次にやること:

cli/src/deploy.ts の中身を、本物の Midnight デプロイに育てていきます。そのとき、今日「眺めるだけ」にした output/contract/index.jskeys/ が、いよいよ主役になります。

つぎに読むページ