アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

Solidityとvyperの変換tips

最近は暇があればcasper FFGのSmart ContractをvyperからSolidityに変換しています。
github.com

作業をしていくにあたり、vyperのコードからSolidityのコードへ変換する際のtipsが思ったよりも溜まってきたので、忘れないうちに記事にしてまとめておきます。

ということで今回の記事はSolidityとvyperの変換tipsです。

1. decimal

vyperには小数を表すdecimalという型が定義されています。ですが、Solidityはまだ小数が扱えません。
ちなみに、ABI定義ではdecimalはfixed168x10になります。詳しい説明は以下のIssueを参照してください。
ABI: consider using the "fixed" type for decimals · Issue #566 · ethereum/vyper · GitHub

で、これはどうやって反映させればいいかというと、データの構造的には下位10桁を小数とみなすという単純なものなので、小数計算時に
> num * 10 ** 10 / den;
という感じで10桁かさ上げして返してあげれば良いです。
という感じのものをlibraryにするとこんな感じになります。
ethfiddle.com

2. decimalを引数にとる関数

decimalを引数にとる関数を変換する時は以下の手順で行いました。

  1. decimalのところをuint168(or uint256)に置き換える
  2. functionを呼び出す時にfunction_idをfixed168x10を引数にとるmethodのfunction_idと置き換える
  3. もしくは、decimal部分の引数を10**10倍して整数として渡す。

自分はもともとpytestで書かれたtestを動かしたかったので、2.の方式を使いました。全体的にSolidityに置き換えて、呼び出し元も全部web3.jsなどに置き換えていくのであれば、3.の方式を使うのがいいかもしれません。3.はon the flyなのでうまく行かないかも。。。

3. struct配列のgetter

例えばこんな感じのものです。

    struct Validator {
        // Used to determine the amount of wei the validator holds. To get the actual
        // amount of wei, multiply this by the deposit_scale_factor.
        Decimal.Data deposit; // : decimal(wei/m),
        int128 start_dynasty;
        int128 end_dynasty;
        bool is_slashed;
        uint total_deposits_at_logout; //: wei_value,
        // The address which the validator's signatures must verify against
        address addr;
        address withdrawal_addr;
    }   

    Validator[] public validators;

これはtuple型として返却されるのですが、実はフロント側のweb3系がまだtupleに対応できていません。そのため、vyperではcompile時にstructのgetterをそれぞれのpropertyごとに分割して定義します。なので、Solidityでも同じように自分で定義してあげます。
ethfiddle.com
こんな感じ。

4. 配列のallocation

vyperではstorageで定義された配列に対して任意の位置にアクセスが可能です。が、Solidityでは一旦配列をアクセスしたい要素の数まで拡張してあげないといけません。コードで示すと次の通りになります。

vyper:

num_array: public(int128[int128])

@public
@constant
def putAt(int128 index, int128 value):
    num_array[index] = value

Solidity:

uint[] public num_array;
function putAt(uint index, int128 value) public view returns(uint) {
    // num_array[index] = value; これはallocateされてない場合もあるのでエラーになる危険がある
    if(num_array.length < (index + 1)) {
        num_array.length = index + 1;
    }
    num_array[index] = value;
}

5. 付録:配列のgetter

vyperでは基本的に数値はすべてint128です(uint256も指定すれば宣言できるけど、デフォルトだとint128ぽい?)。
そのため自動生成されるpublicな配列のgetterの引数の型もint128になります。

Solidityでは以下のように配列を定義します。

 int128[] public num_array;

このように配列のindexの型はsolidityでは明示的に定義できません。なので、vyperのコードから生成したabiとsolidityのコードから生成したabiではこのgetter functionのidが変わります。

で、普通は別にこれで問題が起きることはないのですが、場合によってはvyperのabiを使ってsolidityのsmartContractを動かしたい時があります。そういった時はint128を引数にとるgetterメソッドを自前で定義してあげる必要があります。

コード変換は以下の通りです。
vyper:

dynasty_start_epoch: public(int128[int128])

Solidity:

int128[] _dynasty_start_epoch;
function dynasty_start_epoch(int128 arg0) public returns(int128) {
    return dynasty_start_epoch[uint(arg0)];
}

まとめ

vyperは内部でLLL言語?定義されていて多分ダブルコンパイルしてopcodeを生成しているようです。そのため、Solidityに比べて比較的コーディングがしやすい印象です。
そのため、vyperからSolidityに変換する時は少しコツが必要となります。特にdecimalについてはまだSolidityがfixedに対応していないのでいろいろ大変です。

ただ、両方の言語はABI仕様に準拠しているためそのうち相互に自動変換ができるようになる気もします。