OCamlでEthereumのスマートコントラクトを開発できるようにするプロジェクトの紹介

株式会社 [proof ninja](https://proof-ninja.co.jp/MainJa.html) 技術顧問の池渕です．
proof ninja では，関数型プログラミング言語 OCaml で Ethereum 上のスマートコントラクトを書けるようにするため，**OCaml コードを Ethereum VM (EVM) のバイトコードへ変換するコンパイラ ocaml2evm** を開発中です．
まだまだ開発途上ではありますが，この記事では ocaml2evm の現在の状況・使い方について紹介します．

# ocaml2evm のソースコード，環境準備
ソースコードは以下：

https://github.com/dmm-web3/ocaml2evm

ビルド等のために必要なもの：
- [OCaml](https://ocaml.org/) バージョン 4.14 以上
- [dune](https://github.com/ocaml/dune) (OCaml のビルドシステム)
- [yojson](https://github.com/ocaml-community/yojson), [digestif](https://github.com/mirage/digestif) (OCaml ライブラリ)
- [solc](https://docs.soliditylang.org/en/latest/installing-solidity.html) (Yulコード(後述)のコンパイルのため)
- [npm](https://www.npmjs.com/) (出力したバイトコードが動くことのテストのため．ビルド自体には不要)

使い方：
以下のようなディレクトリ構造があるとします．
```
ocaml2evm/
└─ dir/
   ├─ contracts/
   └─ src/
      ├─ foo.ml
      ...
      └─ bar.ml
```
(ディレクトリ名 `dir`, `src` は自由ですが `contracts` は固定です．)
`ocaml2evm` ディレクトリ下で
```
$ dune exec ocamyul dir/src/foo.ml
```
を実行すると `foo.ml` がコンパイルされ，`dir/contracts/(モジュール名).json` が出力されます．この JSON ファイルには ABI (Application Binary Interface，コントラクトの関数を呼び出すために必要な情報)とバイトコードが含まれており，これを使うことでコントラクトを Ethereum ネットワーク上にデプロイできます．

たとえば，サンプルコード `ocaml2evm/sample/src/erc20.ml` のコンパイル結果が `ocaml2evm/sample/contracts/ERC20.json` です．

# サンプルコード

## 簡単なコントラクトの実装
`ocaml2evm/sample/src` ディレクトリ下にいくつかサンプルの OCaml コードがあります．
たとえば，`ocaml2evm/sample/src/simple_storage.ml` は以下です．

```ocaml
module SimpleStorage : sig
  type storage

  val set : int -> storage -> unit * storage
  val get : unit -> storage -> int * storage
  val incr : unit -> storage -> unit * storage
  val twice : int -> storage -> int * storage
  val anormaltest : int -> storage -> int * storage
end = struct
  type storage = int

  let set n _ = ((), n)
  let get () s = (s, s)
  let incr () s = ((), s + 1)
  let twice n s = (n * 2, s)

  let anormaltest n s =
    let m =
      let l = s * 2 in
      let o = n * 3 in
      l + o
    in
    (m, m)
end
```

ここでは，`SimpleStorage` というモジュールを作り，その中で `set` や `get` などの簡単な関数を定義しています．
まず，`SimpleStorage` ではストレージ(複数の関数呼び出しで共有されるデータの領域)は整数型の値を持つという意味で，
```ocaml
type storage = int
```
のように `storage` 型を定義しています．

各関数は値とストレージを受け取り，値と更新後のストレージのペアを返します．つまり，型は以下の形になっています．
```ocaml
'a -> storage -> 'b * storage
```

たとえば `set` 関数の定義
```ocaml
  let set n _ = ((), n)
```
は整数 `n` を取り，それをストレージに格納し，返り値はなし( = `unit` 型のコンストラクタ `()` を返す)ということを意味します．

その他，
- `get` 関数は「ストレージの値 `s` を取り出して返し，ストレージの変更はなし」，
- `incr` 関数は「ストレージの値をインクリメントして更新する」，
- `twice`関数は「整数 `n` を取ってその二倍を返し，ストレージの変更はなし」
という定義になっています．

関数定義の中では `let ... in` 式が使用でき，関数 `anormaltest` で例を示しています．
`anormaltest` の定義にあるように，ネストした `let ... in` 式も扱えます．

前述したように，このファイルは `ocaml2evm` ディレクトリの中で
```
$ dune exec ocamyulc sample/src/simple_storage.ml
```
を実行することでコンパイルでき，その結果が `sample/contracts/SimpleStorage.json` として出力されます．
JavaScript コード `sample/contract_test/SimpleStorage.test.js` でこの SimpleStorage コントラクトをローカルネットワーク上でテストしています．
テストは `sample` ディレクトリ下で
```
$ npm test
```
で実行できます．

## ERC20 の実装
`sample/src/erc20.ml` で ERC20 を実装しています．
```ocaml
open OCamYul.Primitives

module ERC20 : sig
  type storage
  type mut_storage

  val total_supply : unit -> storage -> int * storage
  val balance_of : address -> storage -> mut_storage -> int * storage
  val allowance : address * address -> storage -> mut_storage -> int * storage
  val mint : int -> storage -> mut_storage -> unit * storage
  val burn : int -> storage -> mut_storage -> unit * storage
  val transfer : address * int -> storage -> mut_storage -> unit * storage
  val approve : address * int -> storage -> mut_storage -> unit * storage

  val transfer_from :
    address * address * int -> storage -> mut_storage -> unit * storage
end = struct
  type storage = int

  type mut_storage =
    (address, int) Hashtbl.t * (address, (address, int) Hashtbl.t) Hashtbl.t

  let total_supply () total = (total, total)

  let balance_of account total (balance, _) =
    (Hashtbl.find balance account, total)

  let allowance (owner, allowed_address) total (_, allow) =
    (Hashtbl.find (Hashtbl.find allow owner) allowed_address, total)

  let mint amount total (balance, _) =
    let from_address = caller () in
    let from_balance = Hashtbl.find balance from_address in
    Hashtbl.replace balance from_address (from_balance + amount);
    ((), total + amount)

  let burn amount total (balance, _) =
    let from_address = caller () in
    let from_balance = Hashtbl.find balance from_address in
    Hashtbl.replace balance from_address (from_balance - amount);
    ((), total - amount)

  let transfer (to_address, amount) total (balance, _) =
    let from_address = caller () in
    let from_balance = Hashtbl.find balance from_address in
    let to_balance = Hashtbl.find balance to_address in
    Hashtbl.replace balance from_address (from_balance - amount);
    Hashtbl.replace balance to_address (to_balance + amount);
    ((), total)

  let approve (allowed_address, amount) total (_, allow) =
    Hashtbl.replace (Hashtbl.find allow (caller ())) allowed_address amount;
    ((), total)

  let transfer_from (from_address, to_address, amount) total (balance, allow) =
    let from_address_allow = Hashtbl.find allow from_address in
    let allowed_balance = Hashtbl.find from_address_allow (caller ()) in
    Hashtbl.replace from_address_allow (caller ()) (allowed_balance - amount);
    let from_balance = Hashtbl.find balance from_address in
    let to_balance = Hashtbl.find balance to_address in
    Hashtbl.replace balance from_address (from_balance - amount);
    Hashtbl.replace balance to_address (to_balance + amount);
    ((), total)
end
```
SimpleStorage と違う点の一つとして，ここでは `storage` の他に `mut_storage` という型を定義しています．
`storage` は整数値のようにイミュータブルな値を格納することを想定しており，一方 `mut_storage` はハッシュテーブルのようなミュータブルな値を格納するために使います．
(OCaml でいうハッシュテーブルは，Solidity でいう mapping に対応します．)

`erc20.ml` では `mut_storage` は二つのハッシュテーブルから成り，一つ目である `(address, int) Hashtbl.t` は口座のアドレスをキー，その口座の残高を値として持つハッシュテーブルを表します．
二つ目である `(address, (address, int) Hashtbl.t) Hashtbl.t` は，キー(口座アドレス) `a` に対して，「それぞれの口座 `b` について `a` は `b` からいくら預金を移してよいかを表すテーブル」を値に持つようなテーブルです．
また，`storage` はこれまでに発行されたトークン全体の量を表す整数を格納します．

たとえば関数 `allowance`
```ocaml
  let allowance (owner, allowed_address) total (_, allow) =
    (Hashtbl.find (Hashtbl.find allow owner) allowed_address, total)
```
は二つの口座アドレス `owner`, `allowed_address` のペアを受け取り，`owner` が `allowed_address` の口座から移せるトークンの量を返しています．

また，関数 `transfer_from`
```ocaml
  let transfer_from (from_address, to_address, amount) total (balance, allow) =
    let from_address_allow = Hashtbl.find allow from_address in
    let allowed_balance = Hashtbl.find from_address_allow (caller ()) in
    Hashtbl.replace from_address_allow (caller ()) (allowed_balance - amount);
    let from_balance = Hashtbl.find balance from_address in
    let to_balance = Hashtbl.find balance to_address in
    Hashtbl.replace balance from_address (from_balance - amount);
    Hashtbl.replace balance to_address (to_balance + amount);
    ((), total)
```
は `from_address` から `to_adress` へ `amount` の量だけトークンを移す，という動作をします．7,8行目の `Hashtbl.replace` で `from_address`/`to_address` の口座残高を `amount` だけ減らし/増やしています．
また，この移動に伴い4行目で関数の呼び出し元(`caller ()`)が `from_address` から移してよいトークン量を減らしています．

# 内部のこと：コンパイルの流れ
コンパイルは，OCaml コードから直接バイトコードを出力しているわけではなく，[Yul](https://docs.soliditylang.org/en/latest/yul.html) という中間言語を介しています．
ocaml2evm のメインの部分は OCaml コードを Yul コードに変換しており，生成された Yul コードは Solidity コンパイラである solc の機能を使ってバイトコードに変換しています．

# 今後など
ocaml2evm コンパイラプロジェクトはまだまだ始動したばかりで，現在は OCaml のごく小さなサブセットしか扱えないのですが，ERC20 を実装できる程度にはなっています．
たとえばまだレコード型や高階関数などは扱えません．
これから扱える機能を増やしていくのはもちろんのこと，今後の展開としては**定理証明支援系 Coq を使った検証**ができるようにすることも目標の一つです．

ということで，開発中である ocaml2evm を紹介してみました．OCaml と Coq でハッピー・スマートコントラクト・ディヴェロッピングを目指そう！！