VyperでPlasma MVPを実装しました

Ryuya Nakamura
Aug 24, 2018 · 15 min read
Merkle Proof Membership Check in Vyper

目次

  • 本プロジェクトの背景・動機
  • Vyperの特徴
  • Plasma MVP in Vyper
  • Vyper開発の経験

背景・動機

Vyperとは、 EVM用に作られたPythonライクなプログラミング言語で、言語をできる限りシンプルに、コードを読みやすいものにし、その結果コントラクトの安全性を高めることが目指されています。

Acknowledgement

今回我々は、OmiseGoチームのPlasma MVPルートチェーンのコントラクトをベースにして実装を行いました。


Vyperの特徴

コントラクトの書き方

  • 1ファイル = 1コントラクト。contract {…} という宣言はしません。
  • 文法はPythonライクです。修飾子は@private, @payableなどとデコレータで、コンストラクタは__init__()と書きます。selfはそのコントラクト自身を表し、ストレージ変数にはself.myData、関数にはself.myFunc()などとアクセスします。
  • 型は必ず明記し、コンパイラが厳しくチェックします。Pythonのtype hintingのような記法で型を表記します。
  • 定義の順番は外部コントラクトのインターフェース→イベント定義→ ストレージ変数定義→関数定義でなければなりません。
  • 後ろで定義された関数を呼ぶことはできません。

Vyperが意図的に持たない機能

先ほど述べたデザイン原則から、Vyperは下記の機能がありません。(詳しい理由などはドキュメントを参照してください!)

  • クラスの継承
  • インラインアセンブリ
  • 関数・演算子のオーバーロード
  • 再帰呼び出し
  • 無限ループ
  • バイナリ固定小数点

Plasma MVP in Solidityとの主な相違点

Vyperの仕様上生まれた実装の違いについて、主要なものを、

  • 現状のVyperの制約によるもの

Vyperの根本的な特徴による違い

前述したような、Vyperの設計思想による特徴や制約から生まれた実装の違いを見ていきます。

assert msg.sender == self.operator
# 1073741824 is 2^30, max size of priority queue is 2^30 - 1
for i in range(1073741824):
if not exitable_at < as_unitless_number(block.timestamp):
break
assembly {
r := mload(add(_sig, 32))
s := mload(add(_sig, 64))
v := byte(0, mload(add(_sig, 96)))
}
r: uint256 = extract32(_sig, 0, type=uint256)
s: uint256 = extract32(_sig, 32, type=uint256)
v: int128 = convert(slice(_sig, start=64, len=1), "int128")
  • 配列にアクセスする時のインデックスや、冪乗の指数にも、整数リテラルしか使えません。

現状のVyperの制約によるもの

Vyperでは、提案自体はapproveされているもののまだ実装されていない機能や、仕様自体が議論中ものが多くあります。このような、今後変わる可能性が大いにあるものの、現状のVyperの仕様上対応が必要だった主な箇所を列挙します。

  • importがないため、root_chainコントラクトの中にpriority_queueコントラクトのインターフェースを書いています
  • Solidityのnewの代わりとしてデプロイ済みのコントラクトを複製するcreate_with_code_of()を用いていますが、現状コンストラクタを実行してくれないため、コンストラクタの代わりとしてsetup()関数を書いて、それをデプロイ後に呼び出すことで代替しています。
  • ストレージ変数の定義で初期値を書くことができないので、コンストラクタで値を入れています。
  • 定数を定義する方法がないので、ハードコードし、コメントで補足する形にしました。
  • 可変長配列がないので、int128/uint256をkeyとしたmapping型を用います。同じく可変サイズのバイト列はないので、バイト列のサイズをあらかじめ制限するか、bytes[1024]などと大きなサイズにしておきます。
  • 構造体型はありますが、構造体の新しい型を定義することはできません。(Solidityはこれができるが構造体型はない) 提案はあります。
  • private関数内ではmsg.senderの値が自身のコントラクトのアドレスになってしまいます。これは現状Vyperでは、メモリアクセスのリスクを考慮しprivate関数をJUMPではなくCALL命令で呼び出しているためです。gasが高くなるという大きな問題もあります。現在他の方法が模索されています
  • その他、unitの扱いが厳密だったり、private関数でも構造体を返せないなどの違いもあります。

Vyperを用いた開発について

具体的にVyperを用いた開発の環境や流れについて説明します。

開発環境

VyperのコンパイラはPythonモジュールです。virtualenvなどでVyper専用の環境を作り、そこにインストールします。docker imageもあります。(参考: Vyperのインストール方法)

テスト

テストはtruffleを使いました。truperというnode.jsのモジュールが、Vyperで書かれたコントラクトからtruffle互換のartifactを作ってくれます。truperでビルドすると、MyContract.vyper.json等というファイル名でartifactが作られてしまいtruffleのマイグレーション(.jsonという拡張子でファイルが作られる)で上書きされなくなってしまうので、npm run buildでtruperでのビルドの後にそのまま.jsonにリネームするようスクリプトを書いています。

苦労したこと

(1) デバッグ

  1. RLPデコードコントラクトをデプロイするために、このアカウントAが署名したトランザクションを送信する

詰まったら。。。

Vyperはまだ情報が少なく、公式のドキュメントが一番の情報源になります。しかし、説明がかなり少ないのと、最新の仕様が反映されていないことが度々あります。

コントリビューション

今回、開発中に気づいたドキュメントの間違いや、機能の提案についてわずかながらコントリビューションをしました。

Vyperの使用感

Vyperを書くのは初めてでしたが、シンプルで覚えることが少なく、書きやすいな、と言う印象です。

最後に

plasma-mvp-vyperへのpull request, issueは大歓迎です!また、記事の間違いやVyper開発のアドバイスなどありましたら、ぜひコメントなどで指摘いただければ幸いです。

LayerX-jp

Evaluate Everything

Ryuya Nakamura

Written by

LayerX-jp

LayerX-jp

Evaluate Everything