Develop with pleasure!

福岡でCloudとかBlockchainとか。

Solidityのソースファイルのレイアウト仕様

Layout of a Solidity Source File — Solidity 0.4.3-develop documentation

Solidityのソースファイルのレイアウト仕様について↑ざっとみておく。

ソースファイルには任意の数のcontractの定義を記述できる。

Version Pragma

ソースファイルにはversion pragmaと呼ばれる注釈を付けることができる。これは将来のコンパイラがバージョンアップした際に互換性の無い変更が導入された際、そのコンパイラでソースがコンパイルされるのを防ぐための機能。基本的に互換性のある形で変更はされるだろうけど、シンタックスの変更などが発生することもある。そのため重大な変更が含まれている場合は、リリースノートのをよく読むようにと。そういったリリースではバージョン番号のx.0.0もしくは0.x.0のxの部分が更新される。

version pragmaは以下のように記載する。

pragma solidity ^0.4.0;

↑のように記載されたソースファイルは、コンパイラのバージョンが0.4.0 よりも小さいバージョンのコンパイラではコンパイルされず、^が付いているので0.5.0以上のコンパイラでも動作しない。こういった指定によって、バージョン0.5.0までは破壊的な変化は起きず、自分が書いたコードが意図した方法でコンパイラされることが保証できる。
ただbugfixのリリースなどがあるのでバージョンを完全に固定はしない(なのでbugfixリリースによってバージョンがあがった0.4.1とかは↑の定義のまま利用可能)。

また、↑よりも複雑なルールを指定することもでき、その方式はnpmで使われるのと同様(package.jsonで指定する書き方)。

他のソースファイルのインポート

シンタックスとセマンティクス

SolidityはJavaScript(ECMAScript6から)のdefault exportのコンセプトと似たimport文をサポートする。

グローバルレベルでは以下の形式でimport文が使える。

import "filename";

このimport文は“filename”から全てのグローバルシンボルをインポートし、現在のグローバルスコープに入れる。(ES6の仕様とは異なる)

import * as symbolName from "filename";

↑は“filename”の全てのグローバルシンボルを持つ、新しいグローバルシンボルsymbolNameを作っている。

import {symbol1 as alias, symbol2} from "filename";

↑は新しいグローバルシンボルaliassymbol2を作成し、それぞれ“filename”からsymbol1symbol2を参照する。

以下の構文はES6の一部ではないが、おそらく便利

import "filename" as symbolName;

↑はimport * as symbolName from "filename";と同じ意味になる。

パス

↑でfilenameと記載したパス名の記載内容について、/ディレクトリセパレータとして、.は現在のディレクトリ、..は1つ上のディレクトリとして扱われる。パス名が.から始まらない場合は絶対パスとして扱われる。

現在のソースファイルと同じディレクトリにあるファイルxをインポートしたい場合は、import "./x" as x;と書く。もしimport "x" as x;と書いたら、グローバル “include directory”内の別のファイルを参照することになる。

実際のパスの解決をどうするかはコンパイラに依存する。一般的に、ディレクトリ階層を厳密にローカルのファイルシステムにマップする必要はなく、ipfsやhttp、git等を経由して検出されたリソースにマップすることもできる。

実際のコンパイラでの使用

コンパイラが実行される際、パスの最初の要素を見つける方法を指定するだけでなく、パスのprefixのリマッピングが可能で、例えばgithub.com/ethereum/dapp-bin/library/usr/local/dapp-bin/libraryにリマップすることができ、コンパイラはそこからファイルを読み込む。さらにこのリマッピングは、コンテキストに依存することができるため、名前は同じライブラリでバージョンが異なるような場合などにおいて、インポートするパッケージを設定できるようになる。

solcの場合

solcでは、このリマッピングcontext:prefix=targetという形で引数で渡される。context:=targetの2つはオプション。全てのリマッピングする値は通常のファイルを指し、その依存関係も含めてコンパイルされる。この仕組みは完全な下位互換を持つ。全てのインポートしたファイルは、引数に記述したcontext以下のprefixで始まるファイルをtargetで指定したprefix`に置換しリダイレクトする。

まぁ実際の例で見たほうが分かりやすい。例えばgithub.com/ethereum/dapp-bin/ locallyをローカルの/usr/local/dapp-binにクローンしている場合、ソースファイル内に以下のように書いておく。

import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;

そしてコンパイル実行時に

$ solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol

と実行する。

もっと複雑な例であれば、いくつかのモジュールが dapp-binのとても古いバージョンに依存していたとする。dapp-binの古いバージョンは/usr/local/dapp-bin_oldにチェックアウトされていて、それを参照するには以下のように指定する。

$ solc module1:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ \
     module2:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin_old/ \
     source.sol

こうすることで古いバージョンは全てmodule2にインポートされ、module1には新しいバージョンをインポートされる。

solcは特定のディレクトリからのみファイルをインクルードできることに注意すること。明示的に指定したソースファイルのディレクトリ及びそのサブディレクトリか、リマッピング対象のディレクトリ及びそのサブディレクトリ。直接絶対パスで指定したい場合は=/のりマッピングを追加する。

browser-solidityの場合

ブラウザベースのコンパイラbrowser-solidityでは、githubのりマッピングは自動的に行われ、ファイルも自動的にネットワーク経由で取得してくれる。以下のようにiterable mappingをインポートできる。

import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;

将来的にgithub以外からもインポートできるようになると思われる。

コメント

単一行のコメントは//で、複数行は/*...*/が使える。

// This is a single-line comment.

/*
This is a
multi-line comment.
*/

まだnatspecコメントと呼ばれるコメントが書ける。これはトリプルスラッシュ///かダブルアスタリスクブロック/** ... */で記述するコメントで、関数宣言やステートメントの上に記述する。このコメント内ではドキュメントを生成する際に使用するDoxygenスタイルのタグが使え、フォーマット検証条件の注釈などが記述できる。

以下の例では、コントラクトのタイトルと2つの入力パラメータ、2つの戻り値についてドキュメント化している。

pragma solidity ^0.4.0;

/** @title Shape calculator.*/
contract shapeCalculator{
    /**@dev Calculates a rectangle's surface and perimeter.
     * @param w Width of the rectangle.
     * @param h Height of the rectangle.
     * @return s The calculated surface.
     * @return p The calculated perimeter.
     */
    function rectangle(uint w, uint h) returns (uint s, uint p) {
        s = w * h;
        p = 2 * (w + h);
    }
}

所感

  • version指定やimportとか当たり前だと思うけどJSをベースにしてるなー。
  • インポートするファイルをソースファイル上ではgitのURLを記述しておいて、コンパイル実行時にその部分をローカルのパスに置き換えることで、ソースファイル上にローカルのファイルシステムのパスが出てこなくなるリマッピング機能、チーム開発とかだと必須。