SCamlによるTezosプログラミング#5

Tezos ブロックチェーン のためのスマートコントラクト記述言語 SCaml、チュートリアルの第5回目です。

今回はついに SCaml でTezosのスマートコントラクトを書きます!

やっと SCaml

やっとです。ブロックチェーンは事前に仮定する情報が多すぎるよね。

https://gitlab.com/dailambda/images/-/raw/master/scaml-page.png

SCaml とは

OCaml で書かれたスマートコントラクトのコードを Tezos の Michelson VM のコードにコンパイルします。言語仕様は OCaml のサブセットです。OCaml を知っていればすぐ書けますし、OCaml の既存の本や資料を読めば SCaml は書けます。OCaml は日本の大学の多くのコンピュータ科学専攻の授業でもプログラミング実習などに使われている良い言語です。

SCaml のプログラムは OCaml のプログラムでもあるので、OCaml コンパイラでコンパイルしてやれば Michelson だけでなく、普通の実行ファイルにコンパイルすることもできますし、JSOO を使うと JavaScript にも落とせます。スマートコントラクトだけでなく、サーバーサイド、クライアントサイドの dApps 開発局面の全てでコードを共通化できます。

また、CoqF* などの形式検証のための言語とも相性が良く、検証されたスマートコントラクトの開発への可能性を持っています。

とりあえずやってみよ

最も簡単な SCaml のスマートコントラクトです。何もしないコントラクトです:

入力
  • パラメータは特にないので () を使います。
  • ストレージも特にありません。 やはり () を使います。
出力
  • ブロックチェーンに関して何も行いません。ですので operation は空のリスト [] です。
  • ストレージは変化しませんので () のままです。

ホントつまらないコントラクトですが、これを SCaml で書くと次のようになります:

open SCaml

let [@entry] main () () =
  ([], ())
open SCaml
SCaml の基本ライブラリに定義された名前空間へのアクセス宣言です。SCaml のスマートコントラクトではまず付けておけば問題ありません。SCaml基本ライブラリを見ればどんな関数が用意されているかわかります。
[@entry]
let [@entry] はスマートコントラクトとして呼び出せる関数「エントリ」の定義に使います。[@entry]アトリビュートのない let宣言はコントラクトとして外部から呼び出すことができません。 [@...] は OCaml のアトリビュートと呼ばれるもので、プリプロセッサや SCaml コンパイラのような OCaml コードを処理するプログラムに処理のヒントを与えるために使われます。
エントリ入力: let [@entry] main () () = ...
エントリ関数は引数を二つ取ります。第一引数は呼び出し時に与えられるパラメータです。第二引数はストレージの中身が与えられます:
let [@entry] エントリ名 パラメータ = ...
エントリ出力
エントリの出力は operation と新ストレージの組みです。ここでは、何も operation は空([])で、ストレージは () のままなので、 ([], ()) になります。 ストレージは入力も出力も二番目に来ると思えばいいでしょう。

そんな感じです。わかりましたか?

コンパイルしてみる

上のコードをエディタで simplest.ml という名前で作成して、SCaml でコンパイルしてみましょう:

$ ./scamlc simplest.ml 
Linking Simplest
Compiling simplest Simplest_0
$

Docker スクリプトの都合上、simplest.mlscamlc スクリプトと同じディレクトリに置いてください。

成功すれば、同じディレクトリに simplest.tz という Michelson のファイルができているはずです:

$ cat simplest.tz 
parameter unit ;
storage unit ;
code { DROP # let param_storage_164 = (input, storage)
            # free param_storage_164
     ; UNIT # let _unit_181 = ()
     ; NIL operation # let _ops_182 = []
     ; PAIR # let _pair_183 = (_ops_182, _unit_181)
            # return _pair_183
     }

うまくいかなかった場合は、あなたが OCaml/SCaml のプログラムのどこかでミスタイプしたからかもしれません。原因を探すより、とりあえず今は、上のコードをコピペしてコンパイルしてみてください。

デプロイする

このコンパイルされた Michelson コードを Tezos のネットワーク上にスマートコントラクトとしてデプロイ(インストールのかっこいい言い方)します。超長いコマンドです:

./tezos-client originate contract simplest \
    transferring 0 from myself \
    running simplest.tz \
    --burn-cap 100
  • デプロイされたスマートコントラクトはウォレット上で simplest というエイリアス名をつけます
  • myself アカウントから初期トークンとして 0 tezzy 送ります
  • スマートコントラクトの Michelson コードは simplest.tz に入っています
  • ストレージ確保のための費用として最大 100 支払います。適当な値です。(本当はほんのちょっとしかかりません)
$ ./tezos-client originate contract simplest transferring 0 from myself running simplest.tz --burn-cap 100
Waiting for the node to be bootstrapped...
Current head: BKxAcD5bE57p (timestamp: 2022-05-19T09:11:50.000-00:00, validation: 2022-05-19T09:12:01.204-00:00)
Node is bootstrapped.
Estimated gas: 1405.942 units (will add 100 for safety)
Estimated storage: 297 bytes added (will add 20 for safety)
Operation successfully injected in the node.
Operation hash is 'ontwST4xWpkc6gR1XaQTZRKDvQLTb1JWEiCLkshhE3YBm5X6TCA'
Waiting for the operation to be included...
Operation found in block: BLfMiFWZNX6ieTVwRVPnLA4j6eThuX4qWGMCTnDBWZjN5YG5jEJ (pass: 3, offset: 1)
This sequence of operations was run:
  Manager signed operations:
    From: tz1Uuf4NLeeEWcX7p7cDCJhPztyoqrdTTuez
    Fee to the baker: ꜩ0.000421
    Expected counter: 10593031
    Gas limit: 1506
    Storage limit: 317 bytes
    Balance updates:
      tz1Uuf4NLeeEWcX7p7cDCJhPztyoqrdTTuez ... -ꜩ0.000421
      payload fees(the block proposer) ....... +ꜩ0.000421
    Origination:
      From: tz1Uuf4NLeeEWcX7p7cDCJhPztyoqrdTTuez
      Credit: ꜩ0
      Script:
        { parameter unit ;
          storage unit ;
          code { DROP ; UNIT ; NIL operation ; PAIR } }
        Initial storage: Unit
        No delegate for this contract
        This origination was successfully applied
        Originated contracts:
          KT1V1nsrRK9KkvJ3AAXLsnkyzDoG3xtDkyh8
        Storage size: 40 bytes
        Paid storage size diff: 40 bytes
        Consumed gas: 1405.942
        Balance updates:
          tz1Uuf4NLeeEWcX7p7cDCJhPztyoqrdTTuez ... -ꜩ0.01
          storage fees ........................... +ꜩ0.01
          tz1Uuf4NLeeEWcX7p7cDCJhPztyoqrdTTuez ... -ꜩ0.06425
          storage fees ........................... +ꜩ0.06425

New contract KT1V1nsrRK9KkvJ3AAXLsnkyzDoG3xtDkyh8 originated.
The operation has only been included 0 blocks ago.
We recommend to wait more.
Use command
  tezos-client wait for ontwST4xWpkc6gR1XaQTZRKDvQLTb1JWEiCLkshhE3YBm5X6TCA to be included --confirmations 1 --branch BMBRRPopGFvHxVQydVxTYYfwSiMK2sPkKjrZbPrqY57nsQSWVTY
and/or an external block explorer.
Contract memorized as simplest.

いろいろ費用を払って、最終的に KT1V1nsrR... というコントラクトアドレスでデプロイできました。(あなたが実際に貰うアドレスはもちろん違います。) このアドレスは tezos-client ウォレットの中で simplest という別名がつけられます。

呼び出してみる

コントラクトの呼び出しは、そのコントラクトに送金することで行われます。送金は、なんだっけ… ./tezos-client transfer .. でした。送金量は 0 でいいです。

$ ./tezos-client transfer 0 from myself to simplest
Waiting for the node to be bootstrapped...
Current head: BKpsALuxDbY4 (timestamp: 2022-05-19T09:13:55.000-00:00, validation: 2022-05-19T09:14:00.492-00:00)
Node is bootstrapped.
Estimated gas: 2050.478 units (will add 100 for safety)
Estimated storage: no bytes added
Operation successfully injected in the node.
Operation hash is 'ooTxLiR5YWqsh6EKUrLybftg8J6PBd2763ke7iLzrkGSWVat5XC'
Waiting for the operation to be included...
Operation found in block: BLXDfnsZW5UHQqudkNcS6WKwrmMEAEap5WaCk3JdeRHCkgiuua6 (pass: 3, offset: 1)
This sequence of operations was run:
  Manager signed operations:
    From: tz1Uuf4NLeeEWcX7p7cDCJhPztyoqrdTTuez
    Fee to the baker: ꜩ0.000467
    Expected counter: 10593032
    Gas limit: 2151
    Storage limit: 0 bytes
    Balance updates:
      tz1Uuf4NLeeEWcX7p7cDCJhPztyoqrdTTuez ... -ꜩ0.000467
      payload fees(the block proposer) ....... +ꜩ0.000467
    Transaction:
      Amount: ꜩ0
      From: tz1Uuf4NLeeEWcX7p7cDCJhPztyoqrdTTuez
      To: KT1V1nsrRK9KkvJ3AAXLsnkyzDoG3xtDkyh8
      This transaction was successfully applied
      Updated storage: Unit
      Storage size: 40 bytes
      Consumed gas: 2050.478

The operation has only been included 0 blocks ago.
We recommend to wait more.
Use command
  tezos-client wait for ooTxLiR5YWqsh6EKUrLybftg8J6PBd2763ke7iLzrkGSWVat5XC to be included --confirmations 1 --branch BL6DX3qTQALh4xsE3hyE5Pt2Ujsz3Excbo4mk7jjXY2qJHWaEKu
and/or an external block explorer.

手数料を ꜩ0.000467 払って、コントラクト実行した結果は:

    Transaction:
      Amount: ꜩ0
      From: tz1Uuf4NLeeEWcX7p7cDCJhPztyoqrdTTuez
      To: KT1V1nsrRK9KkvJ3AAXLsnkyzDoG3xtDkyh8
      This transaction was successfully applied
      Updated storage: Unit
      ..

ストレージが Unit に更新されました。これは SCaml での () のことです。

面白くともなんともない?しょうがないです。何もしないコントラクトだもの。

とりあえずのまとめ

つまらないコントラクトでしたが、Tezos でのコントラクトの作り方を一通り見ることができました:

  • SCaml でコントラクト xxx.ml を書く。
  • ./scamlc xxx.ml でコンパイル、Michelsonコード xxx.tz を得る。
  • `./tezos-client originate contract «エイリアス» transferring «初期トークン» from «支払い元» running «Michelsonファイル名» –burn-cap «100くらい» でコントラクトのデプロイ
  • ./tezos-client transfer «送金量» from «送金元» to «コントラクトアドレス» で呼び出す。

すこしずつ複雑に

これからもうちょっと複雑なコントラクトを書いていきたいと思います。