Solidity¶
Solidityはスマートコントラクトを扱えるオブジェクト指向の高級言語です。スマートコントラクトはEthereum内でアカウントの動作を制御するものです。
SolidityはC++、Python、JavaScriptを参考に、Ethereum Virtual Machine(EVM)の操作を目的に作られています。
Solidityは静的言語で継承やライブラリ、ユーザーが定義した複雑な型をサポートします。
Solidityで投票やクラウドファンディング、匿名のオークションやマルチシグウォレットを作成することができます。
コントラクトをデプロイする際には最新バージョンのSolidityを使うことが推奨されています。これは新しい機能やバグ修正に加えbreaking changesが定期的に導入されているためです。現在0.xバージョンです[これはこの早い変化を示しています。](https://semver.org/#spec-item-4)
Language Documentation¶
もしスマートコントラクトという概念が初めてなのであればSolidityで書かれた スマートコントラクトの例 を推奨します。もっと詳細な情報が欲しい場合は "Solidity by Example" と "Solidity in Depth" セクションを読むことをお勧めします。
ヒント
Remix IDE でいつでもサンプルコードを試すことができます。RemixはブラウザベースのIDEでSolidityでスマートコントラクトが書け、デプロイしてスマートコントラクトを動かすことができます。しばらく時間がかかる場合もありますが、気長にお待ちください。
警告
人間がコードを書いていますのでバグが発生する可能性があります。スマートコントラクトを書く際にはソフトウェア開発のベストプラクティスを参照することをお勧めします。このベストプラクティスはコードレビュー、テスト、audits、correctness proofsを含んでいます。スマートコントラクトのユーザーは時に作成者よりコードそのものを信用します。そしてブロックチェーンとスマートコントラクトはそれぞれ注意すべき特有の問題を抱えているため、production codeに取り掛かる前に Security Considerations セクションを参照してください。
もし疑問があれば、Ethereum Stackexchange か gitter channel で検索もしくは質問してください。
Solidityやこのドキュメントをより良くするアイデアはいつでも大歓迎です。詳細は contributors guide を参照ください。
Contents¶
Introduction to Smart Contracts¶
A Simple Smart Contract¶
まずは値をセットし、他のコントラクトから呼び出せるような基本的なコントラクトの例から始めましょう。今は全てを理解する必要はありません。後ほど細かく説明します。
Storage¶
pragma solidity >=0.4.0 <0.6.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
最初の行はSolidityバージョン0.4.0もしくはそれより新しいものであればこのソースコードが機能を損なわず動作するバージョンで書かれていることを示しています(0.6.0未満のバージョンまで)。これはこのコントラクトが新しい(互換性のない)バージョンのコンパイラでは異なる挙動をする恐れがあるため、コンパイルできないことを明らかにするためです。Pragmaはソースコードをどの様に取り扱うかコンパイラに指示するための一般的な命令です(例:pragma once)。
Solidity上でのコントラクトというのはコード(その function たち)とEthereumブロックチェーン上の特定のアドレスに存在するデータ(その 状態 )の集合です。 uint storedData;
の行では uint
(256bitの符号無し整数)型の storedData
という状態変数を宣言しています。
データベース上で検索可能かつある機能を呼び出すことによって変更可能なデータの様なものだと思ってください。Ethereumの場合、コントラクトが常にそのデータを所有しています。今回の場合、 set
関数と get
関数は変数の値を修正もしくは取得する場合に使用することができます。
状態変数にアクセスするのに他の言語でよく使われる this.
は必要ありません。
このコントラクトでは世界中誰でも数値を格納することが可能ということ以外には機能はありません。そしてあなたにはそれを防ぐ手段はありません。誰でも set
を呼び出して好きな数字を上書きができます。しかし一度セットされた値はブロックチェーン上に残ります。あなただけが数値を変更できる様な制限を加える方法は後ほど説明します。
注釈
全ての識別子(コントラクト名、function名、変数名)はASCII文字に制限されています。UTF-8コードデータを文字列型に格納することは可能です。
警告
似た様な見た目のUnicodeテキストを使う際には注意してください。異なったバイト配列としてエンコードされてしまいます。
Subcurrency Example¶
以下のコントラクトは最も単純な仮想通貨を扱います。コインを0から生成することは可能ですが、コントラクトの作成者だけが実行可能です(異なる発行スキームの実行は簡単です)。さらにユーザー名やパスワードの登録無しに誰でもお互いにコインを送ることができます。必要なのはEthereumのキーペアだけです。
pragma solidity ^0.5.0;
contract Coin {
// "public" というキーワードは値を
// 外部から読み込み可能にさせます。
address public minter;
mapping (address => uint) public balances;
// イベントは軽量クライアントが変更に対する反応を
// 効率的に行うことを可能にします。
event Sent(address from, address to, uint amount);
// これはコントラクトが作られた時にだけ動作する
// コンストラクタです。
constructor() public {
minter = msg.sender;
}
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
require(amount < 1e60);
balances[receiver] += amount;
}
function send(address receiver, uint amount) public {
require(amount <= balances[msg.sender], "Insufficient balance.");
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
このコントラクトはいくつかの新しい機能が備わっていますので、一つずつ見ていきましょう。
address public minter;
と書いてある行はパブリックにアクセス可能なアドレス型の変数を宣言しています。address
型は160ビットの算術演算不可の値です。これはコントラクトのアドレスか外部の人間が持っているキーペアを保存するのに適しています。public
というキーワードは自動的にコントラクトの外側から現在の状態変数の中身にアクセスできる様にする機能を生成します(つまりgetterを生成します)。
このキーワードなしでは他のコントラクトからはこの変数にアクセスできません。
コンパイラで生成されたこの機能は下記のコードとほぼイコールです(今は external
と view
は無視してください):
function minter() external view returns (address) { return minter; }
もちろんファンクション名と状態変数が同じ名前のため、この様なファンクションを追加しても動きませんが、コンパイラがこの様に解釈するということを理解して頂けると幸いです。
次の行の mapping (address => uint) public balances;
は同様にパブリックな状態変数を生成しますが、もう少し複雑なデータタイプです。
これはaddressに符号無しのinteger型を割り当てます。Mappingは hash table として扱うことができます。そしてそれは事実上初期化され、そのため全てのpossible keyは最初から存在し、バイト表現で0となる値に割り当てられます。しかしhash tableと全く同じではありません。mappingではキーや値のリストを取得することはできません。そのため、何をmappingに追加したか覚えておいてください(もしくはリストを保存するか他の高度なデータタイプを使ってください)。もしくはそんなことをしなくて済む様な場合において使用して下さい。
今回の場合 public
で作られた getter function はもう少し複雑でおおまかには下記の様になります:
function balances(address _account) external view returns (uint) {
return balances[_account];
}
見ての通り、あるアカウントの残高をクエリするのにこのfunctionが利用できます。
event Sent(address from, address to, uint amount);
の行は send
ファンクションの最終行でemitされています、いわゆる"event"を宣言しています。ユーザーインターフェース(ともちろんサーバーサイドのアプリケーション)は多くのコストを支払わずにブロックチェーン上でemitされたそれらのイベントをリッスンすることができます。emitされるとすぐにlistenerは from
、to
そして amount
を引数として受け取り、トランザクションをトラックするのに役立ちます。このイベントをリッスンするために下記のJavaScriptコードを使います(Coin
はweb3.jsもしくは似た様なモジュールを用いて作られたコントラクトオブジェクトです。):
Coin.Sent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount +
" coins were sent from " + result.args.from +
" to " + result.args.to + ".");
console.log("Balances now:\n" +
"Sender: " + Coin.balances.call(result.args.from) +
"Receiver: " + Coin.balances.call(result.args.to));
}
})
balances
ファンクションがユーザーインターフェースから自動的にどの様に呼ばれるか確認してください。
コンストラクタはコントラクトが作成される時に1回だけ呼ばれる特別なファンクションで、その後コンストラクタを呼ぶことはできません。このコンストラクタではコントラクトを作った人のアドレスを永久的に保存しています。msg
(tx
と block
も同様に)は特別なグローバル変数で、ブロックチェーンにアクセスできるいくつかのプロパティを含んでいます。msg.sender
は外部からファンクションが呼んだアカウントのアドレスを常に返します。
コントラクトの最後にあり、ユーザもしくはコントラクトによって呼び出される mint
と send
です。
もし mint
がコントラクトを作ったアカウント以外の誰かに呼ばれても何も起きません。これは特別なファンクション require
によって保証されています。これは引数がfalseだった場合に全ての変更を元に戻す機能を持っています。
2つ目の require
は後にオーバーフローを起こす様な大量のコインがないことを保証しています。
一方で、send
は誰にでも(コインを持っていれば)コインを誰かに送ることができます。送るのに十分なコインを持っていなかった場合、require
はプロセスを中止し、適切なエラーメッセージの文字列を返します。
注釈
もしあなたがコインをどこかに送るためにこのコントラクトを使うのであれば、ブロックチェーンエクスプローラ上のアドレスを見ても何も詳細を見ることができません。これはあなたがコインを送り、残高が変わったという事実はこの特定のコインコントラクトのデータストレージにのみ保存されるためです。イベントを使うことで比較的簡単にトランザクションと残高ををトラックする"ブロックチェーンエクスプローラ"を作成することが可能ですが、コインオーナーではなく、コントラクト作成者のあなたがコインコントラクトを検査する必要があります。
Blockchain Basics¶
ブロックチェーンのコンセプトを理解することはプログラマーにとってさほど難しいことではありません。その理由はほとんどの複雑なこと(mining, hashing, elliptic-curve cryptography, peer-to-peer networks, etc.)はただプラットフォームに機能と約束を与えているだけだからです。これらの機能をそういうものとして受け入れれば、内部のテクノロジーについて心配する必要はありません。(AmazonのAWSを使うのに内部でどの様に動作しているか知る必要ありますか?)
Transactions¶
ブロックチェーンはグローバルにシェアされたトランザクションのデータベースです。 つまり誰でもネットワークに接続するだけでこのデータベース上の項目を読み込むことができます。もしデータベース上の何かを変えたいときはいわゆるトランザクションを発行し、他の全員の同意を得る必要があります。トランザクションという言葉はあなたがしたい変更が(例えばあなたが2つの値を同時に変えたいとすると)その両方ともが変わらないか、両方とも変更されることを意味しています。さらに、あなたのトランザクションがデータベースに登録されている最中に他のトランザクションはそのトランザクションを変更することはできません。
例として、ある電子通貨の残高リストのテーブルを想像してください。もしあるアカウントから別のアカウントへの送金がリクエストされた際に、データベースのトランザクションの基本として、もしあるアカウントの残高から送金分が引かれたら、別のアカウントの残高には送金分が常に追加されなければいけません。何かの理由でその別のアカウント残高に送金分が追加されないのであれば、送金元のアカウントの残高も元のままでなければいけません。
更にトランザクションは常に送信者(作成者)によって暗号学的に署名されます。これによりデータベースのある種の改ざんを防ぐことができます。電子通貨の例で言えば、単純なチェックでキーを持っている人だけがお金を送ることができます。
Blocks¶
解決しなければならない大きな問題の一つとして(Bitcoinの用語で)"二重支払い攻撃"があります。もしあるアカウントを空にする様な2つのトランザクションが同時に存在していたらどうなるでしょうか。基本的には最初に承認された最初のトランザクションのみが有効です。しかし問題は"最初の"というのはpeer-to-peerネットワークにおいて客観的ではないのです。
理論的にはこの問題は気にする必要がありません。グローバルに承認された順番のトランザクションが選ばれ、このコンフリクトが解消します。いくつかのトランザクションはブロックと言われるもので一まとめにされ、全ての参加しているノードの間で処理されます。 もし2つの矛盾したトランザクションがあった場合には、2つ目のトランザクションはリジェクトされブロックの一部として組み込まれることはありません。
これらのブロックは一つのシーケンスを作るためブロックチェーンという名前がつけられました。ブロックは定期的に追加され、Ethereumでは約17秒ごとに1つ追加されます。
順序選択メカニズム(マイニング)では、ブロックが取り消されることもあります。しかしこれはチェーンの先端でだけで起こり、ブロックが追加されるごとに取り消される可能性が減ります。そのため、あなたのトランザクションは取り消されるもしくは削除される可能性もありますが、長く待てば待つほどその可能性は低くなります。
注釈
トランザクションは次のブロックやある特定の未来のブロックに組み込まれる保証はありません。これはトランザクションを送った人にではなく、マイナーにどのトランザクションをブロックに組み込むかの権限があるためです。
もしあなたのコントラクトである未来の時間でコールしたい場合には alarm clock もしくは似た様なoracleのサービスが使用可能です。
The Ethereum Virtual Machine¶
Overview¶
Ethereum Virtual Machine(EVM)はEthereum上のスマートコントラクトのためのruntime環境です。サンドボックス化されているだけでなく、実際には完全に独立しています。つまりEVM内部のコードはネットワークやファイルシステム、または他のプロセスにアクセスしません。 スマートコントラクトですら他のスマートコントラクトへのアクセスは制限されています。
Accounts¶
Ethereumには2種類のアカウントがあります。両方とも同じアドレスを共有しています。外部アカウント は公開・秘密鍵のペアで管理されており、コントラクトアカウント はアカウントと一緒に保存されたコードによってコントロールされています。
外部アカウントのアドレスは公開鍵から決まる一方で、コントラクトのアドレスはコントラクトが作られた時に決まります。(コントラクトの作成者のアドレスと送られたトランザクションの数いわゆる"nonce"によって決まります。)
アカウントがコードを保存するかどうかに関わらず、EVMはこの2つのタイプを同様に扱います。
全てのアカウントは storage という256ビットのワードにmappingされた256ビットのkey-valueを持っています。
さらに、全てのアカウントは balance をEther("Wei"でいうと 1 ether は 10**18 wei です)で持っており、Etherを含んだトランザクションを送ることでこの値は変化します。
Transactions¶
トランザクションはあるアカウントから別のアカウント(これは同じアカウントもしくは空のアカウントの場合もある。下記をご参照ください)へのメッセージです。これはバイナリーデータ("payload"と呼ばれます)とEtherを含んでいます。
送信先のアカウントがコードを含んでいた場合、そのコードは実行され、payloadはインプットデータとして提供されます。
もし送信先のアカウントがセットされていなかったら(トランザクションが受信者情報を持っていないか、受信者が null
だった場合には)、トランザクションは 新しいコントラクト を生成します。先にも言及した通り、コントラクトのアドレスはゼロアドレスではなく送信者やトランザクションの数(nonce)によって決まります。
この様なコントラクト作成のトランザクションのpayloadはEVM bytecodeに変換され、実行されます。この実行のアウトプットデータはコントラクトのコードとして永久的に保存されます。
これが意味するのはコントラクトを生成するために実際のコントラクトのコードを送るのではなく、コードが実行された時にそのコードを返すコードを送っています。
注釈
コントラクトが作られている間、そのコードはまだ空です。そのため、コンストラクタの実行が終了するまで、作成中のこのコントラクトを呼ぶべきではありません。
Gas¶
トランザクションの生成にあたり、各トランザクションはある量の gas を要求します。この目的は必要な処理の量を制限し、この処理に対しての報酬を同時に行うためです。EVMがトランザクションを実行している間、gasはあるルールに則り、徐々に減っていきます。
gas price とはトランザクションの作成者によってセットされる値であり、この作成者は gas_price * gas
を送信するアカウントから支払う必要があります。もしトランザクションの実行後にgasが残っていたら、作成者に返金されます。
もしgasはある値より多く使われたら(負の値になりえます)、gas不足の例外が投げられ、現在の呼び出されたフレーム内での変更は全て取り消されます。
Storage, Memory and the Stack¶
Ethereum Virtual Machineはデータを保存できる場所が3つあります。それはstorage、memory、stackです。以下で説明していきます。
各アカウントは storage と呼ばれるデータエリアを持っており、functionの呼び出しからトランザクションまで残ります。 Storageは256bitのワードを256bitのワードにマッピングしているkey-value storeです。 コントラクト内ではstorageを列挙することはできません。また、storageの読み込みは比較的高価ですし、変更はさらに高価です。コントラクト外からstorageを読み書きすることはできません。
2つ目のデータエリアは memory と呼ばれ、コントラクトは各メッセージの呼び出しに対してクリアされたインスタンスを取得します。memoryはバイトレベルのリニアアドレスですが、読み取りは256bitに制限され、書き込みは8bitもしくは256bitに制限されます。過去に変更がないmemoryの単語にアクセスした際にmemoryは256-bitの単語に拡張されます(例えば単語のオフセット)。拡張の際にはgasは支払われます。memoryは成長すればするほど高くなります。(二次関数的に大きくなります。)
EVMは登録機械ではなくstack machineです。そのため全ての計算は stack と呼ばれるデータエリアで行われます。最大1024要素であり、256-bitの単語を含みます。stackへのアクセスは下記のようにトップエンドに制限されます。 トップの16要素の内の1要素を一番トップの要素にコピーするか、一番トップの要素をその下の16要素の内の一つと交換することができます。他のオペレーションはstackからトップ二つの要素(オペレーションによるが一つか二つ以上の場合もある)を取り出し、stackに追加します。 もちろん、stackの深い要素にアクセスするために、stackの要素をstorageやmemoryに移動するのは可能です。 しかし、stackのトップを最初に削除しないでstackの深いところにある任意の要素にアクセスすることはできません。
Instruction Set¶
EVMのインストラクションは間違った、もしくは矛盾したコンセンサス問題を起こしうる実行を避けるために最小限に保たれています。 全てのインストラクションは基本的なデータタイプ、256-bitのワードもしくはmemory(もしくは他のバイト配列)の元で成り立っています。
基本的な算術、ビット、論理、比較計算は使うことができます。条件付き分岐も可能です。更にコントラクトはブロック番号やタイムスタンプの様なプロパティにアクセスできます。
完全な表は list of opcodes をインラインアセンブリのドキュメントの一部として参照下さい。
Message Calls¶
コントラクトは他のコントラクトを呼び出したり、message callを使ってEtherをコントラクトアカウントではないアカウントに送ることができます。message callはソース、送信先、データpayload、Ether、gas、返り値がある点でトランザクションに似ています。実際に全てのトランザクションは次のメッセージコールを作るトップレベルメッセージコールで構成されています。
コントラクトは残っている gas をどれだけ送るか、そしてどのくらい残すかを内部のmessage callで決めることができます。内部呼び出しでgas不足の例外(もしくは他の例外)が発生したら、スタックに追加されることによりエラーが伝えられます。この場合、呼び出しと一緒に送られたgasのみが使用されます。その様な状況においてSolidityではデフォルトでコントラクトの呼び出しは手動の例外を起こし、例外は呼び出しのスタックから呼び出されます。
既に議論した様に、呼び出されたコントラクト(呼び出し元と同じになる場合もあります)はクリアされたmemoryのインスタンスを受け取り、calldata と呼ばれる別のエリアにあるコールpayloadにアクセスできます。 このコントラクト実行後に、このコントラクトは呼び出し元が事前に割り振ったmemoryの場所に保存されていたデータを返します。 これら全ての呼び出しは同時に起きます。
呼び出しは1024の深さに 制限 されます。これが意味するのはもっと複雑な運用においてループ処理は再帰的な呼び出しより好まれるということです。更に、63/64番目のgasだけはmessage callの中に送られるため、実際には深さは1000より少し小さくなります。
Delegatecall / Callcode and Libraries¶
delegatecall と呼ばれる特別なメッセージコールの変異型があります。これはメッセージコールと同じですが、送信先のアドレスのコードが呼び出し元のコントラクトのコンテキストで実行されるということと msg.sender
と msg.value
はその値を変えません。
つまりコントラクトは動的に違うアドレスからコードをロードできるということです。Storage、つまり現在のアドレスとバランスはまだ呼び出し元のコントラクトを参照していますが、コードだけは呼び出されたアドレスから取得されています。
これはSolidityにおいて"ライブラリ"機能を実装可能としています。例えば複雑なデータ構造を実行するために、再利用可能なライブラリのコードをコントラクトのstorageに保存できます。
Logs¶
特別にインデックスされ、ブロックレベルで全てマッピングされたデータ構造の中にデータを保存することができます。この logs と呼ばれる機能は events を実行するためにSolidityによって使用されています。コントラクトは作成後はログデータにアクセスできませんがブロックチェーンの外側から効率的にアクセスできます。いくつかのログデータは bloom filters に保存されるため、このデータは効率的かつ暗号学的に安全な方法で検索できます。そのためブロックチェーン全てをダウンロードしていないネットワーク上のpeer(いわゆる"light clients")でもこれらのログを見つけることがきます。
Create¶
コントラクトは特別なopcodeを使って他のコントラクトを作ることもできます(コントラクトはトランザクションがする様に単純にゼロアドレスをコールしません)。これら create calls と通常のmessage callの唯一の違いはpayloadデータが実行され、結果がコードとして保存され、呼び出し元と作成者がスタック上にある新しいコントラクトのアドレスを受け取ります。
Deactivate and Self-destruct¶
ブロックチェーン からコードを削除する唯一の手段はコントラクトが selfdestruct
を実行する時のみです。そのアドレスに残っているEtherが設定されていた送信先に送られた時にstorageとコードは削除されます。コントラクトの削除は理論上は良いアイデアの様に聞こえますが、潜在的に危険をはらんでいます。誰かが削除されたコントラクトにEtherを送り、そのEtherは永遠に失われる様なことが起こり得ます。
注釈
もしコントラクトのコードが selfdestruct
を含んでいなかったとしても、delegatecall
もしくは callcode
を使うことで実行可能です。
もしコントラクトを無効化したいのであれば、代わりに全てのfunctionを元に戻させる内部の状態(機能)を変更することでコントラクトを無効化すべきです。これによりコントラクトがEtherを返すとすぐにそのコントラクトを使えなくします。
警告
もしコントラクトを"selfdestruct"で削除したとしても、ブロックチェーン上の履歴には残りますし、きっとほぼ全てのノードにより保持されます。つまり"selfdestruct"はハードディスクからデータを消すのとは異なるということです。
Installing the Solidity Compiler¶
Versioning¶
Solidityのバージョンは semantic versioning に準じており、リリースに加え、nightly development builds も使用可能となっています。ドキュメントに含んでいないものや大きな変更を含んだ成果が組み込まれているものの、nightly buildsは動作を保証されていません。 最新バージョンを使用することをお薦めします。下記のパッケージインストーラでは最新バージョンを使用しています。
Remix¶
小規模のコントラクトやさっとSolidityを学ぶのにRemixをお薦めしています。
Remix onlineにアクセスして下さい。何もインストールする必要はありません。
もしインターネット接続無しで使用したい場合は https://github.com/ethereum/remix-live/tree/gh-pages にアクセスし、指示に従って .zip
ファイルをダウンロードして下さい。
追加オプションとしてこのページではコマンドラインのSolidityコンパイラのインストール方法を紹介しています。大規模なコントラクトやもっと複雑なコンパイルオプションが必要な場合にはコマンドラインコンパイラを使用してください。
npm / Node.js¶
Solidityコンパイラである solcjs の便利で簡単なインストール方法として npm を使用してください。solcjs はこの後紹介するコンパイラへのアクセス方法に比べて機能は少ないです。Using the Commandline Compiler のドキュメンテーションでは全ての機能が備わっている solc というコンパイラを使用します。solcjs の使用方法はその レポジトリ にドキュメント化されています。
注意: solc-jsプロジェクトはEmscriptenを使うことでC++ solc から派生されています。つまり両方とも同じコンパイラのソースコードを使用しているということです。solc-js はJavascriptのプロジェクトで(Remixの様に)直接使用可能です。使い方はsolc-jsレポジトリを参照して下さい。
npm install -g solc
注釈
コマンドラインの実行ファイル名は solcjs です。
solcjs のコマンドラインオプションは solc と互換性がなく、solc では動く様なツール(例えば geth )は solcjs では動作しません。
Docker¶
コンパイラ用に最新のdockerビルドが提供されています。stable
レポジトリはリリースされたバージョンを含み、nightly
レポジトリはdevelopブランチに潜在的に不安定になりうる変更を含んでいます。
docker run ethereum/solc:stable --version
現在、dockerイメージはコンパイラ実行ファイルだけを含んでいます。そのためソースとアウトプットディレクトリをリンクする作業が必要です。
Binary Packages¶
Solidityのバイナリパッケージは solidity/releases で利用可能です。
更にUbuntu用にPPA(Personal Package Archive)もあるので、下記のコマンドで最新の安定バージョンを取得可能です:
sudo add-apt-repository ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install solc
nightlyバージョンは下記のコマンドでインストール可能です:
sudo add-apt-repository ppa:ethereum/ethereum
sudo add-apt-repository ppa:ethereum/ethereum-dev
sudo apt-get update
sudo apt-get install solc
snap package もリリースしています。これは全ての supported Linuxディストリビューション でインストール可能です。solcの最新の安定バージョンは下記でインストールできます。
sudo snap install solc
もし最新のSolidityのdevelopmentバージョンの最近の変更のテストを手伝って頂けるのであれば、下記を使用してください:
sudo snap install solc --edge
最新developmentバージョンに限られますが、Arch Linuxもパッケージがあります:
pacman -S solidity
Homebrewでbuild-from-sourceとしてSolidityのコンパイラを提供しています。Pre-built bottlesは現在サポートされていません。
brew update
brew upgrade
brew tap ethereum/ethereum
brew install solidity
もしSolidityの特定バージョンが必要であればHomebrew formulaをGithubから直接インストールできます。
solidity.rb commits on Github を確認して下さい。
solidity.rb
の特定のコミットのraw file linkを持つまでは過去のリンクを参照して下さい。
brew
を使用してインストールして下さい:
brew unlink solidity
# Install 0.4.8
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
Gentoo Linuxも emerge
を使用してインストール可能なSolidityのパッケージを提供しています:
emerge dev-lang/solidity
Building from Source¶
Prerequisites - Linux¶
SolidityのLinux buildのために下記のdependenciesをインストールする必要があります:
Software | Notes |
---|---|
Git for Linux | Command-line tool for retrieving source from Github. |
Prerequisites - macOS¶
macOS用に最新バージョンの Xcodeがインストールされている ことを確認して下さい。これには Clang C++ compiler と Xcode IDE、それに他のOS XでC++アプリを開発するのに必要なAppleのdevelopmentツールが含まれています。もしXcodeをインストールするのが初めて、もしくは新しいバージョンをインストールしたばかりなのであれば、コマンドラインbuildsをする前にライセンスに同意する必要があります:
sudo xcodebuild -license accept
私たちのOS X buildsは外部のdependenciesをインストールするのに Homebrew package managerのインストール を要求しています。もし始めから行いたい場合は、こちらが Homebrewのアンインストール 方法です。
Prerequisites - Windows¶
SolidityのWindows buildsに下記のdependenciesのインストールが必要です:
Software | Notes |
---|---|
Git for Windows | Command-line tool for retrieving source from Github. |
CMake | Cross-platform build file generator. |
Visual Studio 2017 Build Tools | C++ compiler |
Visual Studio 2017 (Optional) | C++ compiler and dev environment. |
もし既にIDEを持っており、コンパイラとライブラリだけが必要な場合には、Visual Studio 2017ビルドツールをインストールできます。
Visual Studio 2017はIDEと必要なコンパイラとライブラリを提供しています。そのためもしIDEを持っておらずSolidityの開発を行いたい場合にはVisual Studio 2017は全てを簡単にセットアップするための選択肢かもしれません。
こちらがVisual Studio 2017 Build ToolsもしくはVisual Studio 2017でインストールされるコンポーネントのリストです。
- Visual Studio C++ core features
- VC++ 2017 v141 toolset (x86,x64)
- Windows Universal CRT SDK
- Windows 8.1 SDK
- C++/CLI support
Clone the Repository¶
ソースコードをクローンするのに下記のコマンドを実行して下さい:
git clone --recursive https://github.com/ethereum/solidity.git
cd solidity
もしSolidityの開発に助力頂けるのであればSolidityをforkしてセカンドリモートとしてあなたの個人的なforkを追加して下さい:
git remote add personal git@github.com:[username]/solidity.git
External Dependencies¶
macOS、Windows、多数のLinuxディストリビューションで必要な全ての外部dependenciesをインストールするヘルパースクリプトがあります。
./scripts/install_deps.sh
もしくはWindows上では:
scripts\install_deps.bat
Command-Line Build¶
開発を始める前に外部dependenciesをインストールするのを忘れないでください(上記参照)。
Solidityプロジェクトはビルドを設定するためにCMakeを使っています。繰り返して行うbuildを高速化するためにccacheをインストールした方が良いでしょう。CMakeは自動的にccacheをピックアップします。 SolidityのビルドはLinux、macOSや他のUniX上ではほぼ同じです。
mkdir build
cd build
cmake .. && make
もしくはもっと簡単に:
#note: これはbinaries solcとsoltestをusr/local/bin
./scripts/build.sh上にインストールします。
そしてWindowsでは:
mkdir build
cd build
cmake -G "Visual Studio 15 2017 Win64" ..
後半のやり方ではbuildディレクトリに solidity.sln を作成します。これをダブルクリックするとVisual Studioが起動するはずです。Release configurationをビルドすることをお薦めしますが、他は全て動作します。
他の方法として、Windowsのコマンドラインでもビルドできます:
cmake --build . --config Release
CMake options¶
もし何のCMakeオプションが使用可能か興味があるのであれば cmake .. -LH
を動かしてください。
SMT Solvers¶
Solidityはデフォルトでシステム内でSMT solversがあれば、それを使ってビルドすることができます(デフォルトで使用します)。cmake オプションで全てのsolverは無効にできます。
注意: いくつかの例においては潜在的にビルドの失敗を引き起こす場合があります。
デフォルトで有効になっていますが、buildフォルダ内ではsolverは無効にできます:
# disables only Z3 SMT Solver.
cmake .. -DUSE_Z3=OFF
# disables only CVC4 SMT Solver.
cmake .. -DUSE_CVC4=OFF
# disables both Z3 and CVC4
cmake .. -DUSE_CVC4=OFF -DUSE_Z3=OFF
The version string in detail¶
Solidityのバージョン文字列は4つの要素を含んでいます:
- バージョンナンバー
- プレリリースタグ、通常
develop.YYYY.MM.DD
もしくはnightly.YYYY.MM.DD
commit.GITHASH
のフォーマットでコミット- 任意の数のアイテムを持ったプラットフォームで、そのプラットフォームとコンパイラの詳細が記述されている
もしローカルな修正があった場合にはコミットは .mod
という接尾辞がつきます。
これらの要素はSemverの要求通りに結合され、SolidityのプレリリースタグはSemverのプレリリースに相当します。そしてSolidityのコミットとプラットフォームの2つでSemver buildのメタデータを作ります。
リリースの例: 0.4.8+commit.60cc1668.Emscripten.clang
.
プレリリースの例: 0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang
Important information about versioning¶
リリース後はパッチバージョンのレベルは上がっていきます。これはパッチレベルの変更だけは進んでいくと考えているからです。変更がマージされたらバージョンはsemverと変更の重要度により
上がっていきます。最後に、リリースは常に現在のnightly buildのバージョンでされますが、prerelease
の指定はありません。
例:
- 0.4.0版がリリース
- nightly buildの0.4.1版ができる
- 大きな変更がないので、バージョンの変更はなし
- 大きな変更があるので、バージョンが0.5.0に上がる
- 0.5.0がリリースされる
このバージョニングは version pragma でちゃんと動作します。
Solidity by Example¶
Voting¶
次のコントラクトは割と複雑ですが、Solidityのたくさんの機能を表しています。そのコントラクトは投票を実行しています。もちろん、電子投票のメインの問題はどうやって投票権を正しい人に渡すのかということと、どうやって不正操作を防ぐかです。ここでは全ての問題を解決はしませんが、投票がどの様に行われ、そして 自動かつ公正な 投票数のカウントの方法をお見せします。
そのアイデアとは投票ごとにコントラクトを作り、オプションごとに短い名前をつけるものです。そしてコントラクトの作成者は管理者として各アドレスに投票の権利を付与します。
そのアドレスを持っている人は自分で投票するか、投票の権利を信頼している人に譲渡することもできます。
投票の最後に、winningProposal()
は現在の最高投票数を獲得している人を表示します。
pragma solidity >=0.4.22 <0.6.0;
/// @title Voting with delegation.
contract Ballot {
// This declares a new complex type which will
// be used for variables later.
// It will represent a single voter.
struct Voter {
uint weight; // weight is accumulated by delegation
bool voted; // if true, that person already voted
address delegate; // person delegated to
uint vote; // index of the voted proposal
}
// This is a type for a single proposal.
struct Proposal {
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}
address public chairperson;
// This declares a state variable that
// stores a `Voter` struct for each possible address.
mapping(address => Voter) public voters;
// A dynamically-sized array of `Proposal` structs.
Proposal[] public proposals;
/// Create a new ballot to choose one of `proposalNames`.
constructor(bytes32[] memory proposalNames) public {
chairperson = msg.sender;
voters[chairperson].weight = 1;
// For each of the provided proposal names,
// create a new proposal object and add it
// to the end of the array.
for (uint i = 0; i < proposalNames.length; i++) {
// `Proposal({...})` creates a temporary
// Proposal object and `proposals.push(...)`
// appends it to the end of `proposals`.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
// Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`.
function giveRightToVote(address voter) public {
// If the first argument of `require` evaluates
// to `false`, execution terminates and all
// changes to the state and to Ether balances
// are reverted.
// This used to consume all gas in old EVM versions, but
// not anymore.
// It is often a good idea to use `require` to check if
// functions are called correctly.
// As a second argument, you can also provide an
// explanation about what went wrong.
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
/// Delegate your vote to the voter `to`.
function delegate(address to) public {
// assigns reference
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
// Forward the delegation as long as
// `to` also delegated.
// In general, such loops are very dangerous,
// because if they run too long, they might
// need more gas than is available in a block.
// In this case, the delegation will not be executed,
// but in other situations, such loops might
// cause a contract to get "stuck" completely.
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// We found a loop in the delegation, not allowed.
require(to != msg.sender, "Found loop in delegation.");
}
// Since `sender` is a reference, this
// modifies `voters[msg.sender].voted`
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate_.weight += sender.weight;
}
}
/// Give your vote (including votes delegated to you)
/// to proposal `proposals[proposal].name`.
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// If `proposal` is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}
/// @dev Computes the winning proposal taking all
/// previous votes into account.
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
// Calls winningProposal() function to get the index
// of the winner contained in the proposals array and then
// returns the name of the winner
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
Blind Auction¶
このセクションではEthereum上で完全なブラインドオークションを作成するのがいかに簡単かお見せします。
Simple Open Auction¶
全員が期間内に入札できるというのが次の単純なオークションのコントラクトの大まかな考え方です。オークション参加者が入札を取り消すことが無いようにするために入札はお金/etherを既に含んでいます。もし最高額が更新されたらその前の最高額を入札していた人に返金します。入札の金額の受領者がお金を受け取るために、コントラクトは入札期間終了後にマニュアルで呼び出されなければいけません。コントラクトは自動でこれを行えません。
pragma solidity >=0.4.22 <0.6.0;
contract SimpleAuction {
// Parameters of the auction. Times are either
// absolute unix timestamps (seconds since 1970-01-01)
// or time periods in seconds.
address payable public beneficiary;
uint public auctionEndTime;
// Current state of the auction.
address public highestBidder;
uint public highestBid;
// Allowed withdrawals of previous bids
mapping(address => uint) pendingReturns;
// Set to true at the end, disallows any change.
// By default initialized to `false`.
bool ended;
// Events that will be emitted on changes.
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
// The following is a so-called natspec comment,
// recognizable by the three slashes.
// It will be shown when the user is asked to
// confirm a transaction.
/// Create a simple auction with `_biddingTime`
/// seconds bidding time on behalf of the
/// beneficiary address `_beneficiary`.
constructor(
uint _biddingTime,
address payable _beneficiary
) public {
beneficiary = _beneficiary;
auctionEndTime = now + _biddingTime;
}
/// Bid on the auction with the value sent
/// together with this transaction.
/// The value will only be refunded if the
/// auction is not won.
function bid() public payable {
// No arguments are necessary, all
// information is already part of
// the transaction. The keyword payable
// is required for the function to
// be able to receive Ether.
// Revert the call if the bidding
// period is over.
require(
now <= auctionEndTime,
"Auction already ended."
);
// If the bid is not higher, send the
// money back.
require(
msg.value > highestBid,
"There already is a higher bid."
);
if (highestBid != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
/// Withdraw a bid that was overbid.
function withdraw() public returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns.
pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd() public {
// It is a good guideline to structure functions that interact
// with other contracts (i.e. they call functions or send Ether)
// into three phases:
// 1. checking conditions
// 2. performing actions (potentially changing conditions)
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
// effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.
// 1. Conditions
require(now >= auctionEndTime, "Auction not yet ended.");
require(!ended, "auctionEnd has already been called.");
// 2. Effects
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// 3. Interaction
beneficiary.transfer(highestBid);
}
}
Blind Auction¶
前のオープンオークションを次はブラインドオークションに拡張します。ブラインドオークションのメリットは入札期限間際の時間に対するプレッシャーが無いことです。ブラインドオークションを誰からも見れるプラットフォームで行うというのは変な感じがしますが、暗号学が助けてくれます。
入札期間 に入札者は実際に入札を行いません。しかし、ハッシュ化されたものだけ送ります。現在、(十分に長い)二つのハッシュ値が同じ値を見つけるのは実用上不可能なので、入札者はその入札値を変更することはできません。 入札期間の終了後入札者は入札値を公開する必要があります。暗号化されていない値を送り、コントラクトはその値をハッシュ化したものが入札期間に送られたハッシュ値と同じか確認します。
もう一つの問題はどうやってオークションに 強制力を持たせ、かつブラインド にするかです。落札した入札者が送金しないことを防ぐ唯一の方法は入札時にお金を支払わせることです。Ethereumでは送金はオープンなので誰でも金額を見ることができます。
次のコントラクトではこの問題を最高額の入札以上のどんな値でも受け入れることで解決しています。もちろんこれは値が公開された時にしか行えないので、いくつかの入札は無効の可能性があり、かつそれは故意的な可能性もあります(高額送金を伴った無効な入札をするために明らかなフラグを立てることもあります)。入札者は高いもしくは低い入札で他の入札者を混乱させることができます。
pragma solidity >0.4.23 <0.6.0;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}
address payable public beneficiary;
uint public biddingEnd;
uint public revealEnd;
bool public ended;
mapping(address => Bid[]) public bids;
address public highestBidder;
uint public highestBid;
// Allowed withdrawals of previous bids
mapping(address => uint) pendingReturns;
event AuctionEnded(address winner, uint highestBid);
/// Modifiers are a convenient way to validate inputs to
/// functions. `onlyBefore` is applied to `bid` below:
/// The new function body is the modifier's body where
/// `_` is replaced by the old function body.
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }
constructor(
uint _biddingTime,
uint _revealTime,
address payable _beneficiary
) public {
beneficiary = _beneficiary;
biddingEnd = now + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}
/// Place a blinded bid with `_blindedBid` =
/// keccak256(abi.encodePacked(value, fake, secret)).
/// The sent ether is only refunded if the bid is correctly
/// revealed in the revealing phase. The bid is valid if the
/// ether sent together with the bid is at least "value" and
/// "fake" is not true. Setting "fake" to true and sending
/// not the exact amount are ways to hide the real bid but
/// still make the required deposit. The same address can
/// place multiple bids.
function bid(bytes32 _blindedBid)
public
payable
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: _blindedBid,
deposit: msg.value
}));
}
/// Reveal your blinded bids. You will get a refund for all
/// correctly blinded invalid bids and for all bids except for
/// the totally highest.
function reveal(
uint[] memory _values,
bool[] memory _fake,
bytes32[] memory _secret
)
public
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
require(_values.length == length);
require(_fake.length == length);
require(_secret.length == length);
uint refund;
for (uint i = 0; i < length; i++) {
Bid storage bidToCheck = bids[msg.sender][i];
(uint value, bool fake, bytes32 secret) =
(_values[i], _fake[i], _secret[i]);
if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) {
// Bid was not actually revealed.
// Do not refund deposit.
continue;
}
refund += bidToCheck.deposit;
if (!fake && bidToCheck.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
// Make it impossible for the sender to re-claim
// the same deposit.
bidToCheck.blindedBid = bytes32(0);
}
msg.sender.transfer(refund);
}
// This is an "internal" function which means that it
// can only be called from the contract itself (or from
// derived contracts).
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// Refund the previously highest bidder.
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
/// Withdraw a bid that was overbid.
function withdraw() public {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `transfer` returns (see the remark above about
// conditions -> effects -> interaction).
pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd()
public
onlyAfter(revealEnd)
{
require(!ended);
emit AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
}
Safe Remote Purchase¶
pragma solidity >=0.4.22 <0.6.0;
contract Purchase {
uint public value;
address payable public seller;
address payable public buyer;
enum State { Created, Locked, Inactive }
State public state;
// Ensure that `msg.value` is an even number.
// Division will truncate if it is an odd number.
// Check via multiplication that it wasn't an odd number.
constructor() public payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value, "Value has to be even.");
}
modifier condition(bool _condition) {
require(_condition);
_;
}
modifier onlyBuyer() {
require(
msg.sender == buyer,
"Only buyer can call this."
);
_;
}
modifier onlySeller() {
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
modifier inState(State _state) {
require(
state == _state,
"Invalid state."
);
_;
}
event Aborted();
event PurchaseConfirmed();
event ItemReceived();
/// Abort the purchase and reclaim the ether.
/// Can only be called by the seller before
/// the contract is locked.
function abort()
public
onlySeller
inState(State.Created)
{
emit Aborted();
state = State.Inactive;
seller.transfer(address(this).balance);
}
/// Confirm the purchase as buyer.
/// Transaction has to include `2 * value` ether.
/// The ether will be locked until confirmReceived
/// is called.
function confirmPurchase()
public
inState(State.Created)
condition(msg.value == (2 * value))
payable
{
emit PurchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}
/// Confirm that you (the buyer) received the item.
/// This will release the locked ether.
function confirmReceived()
public
onlyBuyer
inState(State.Locked)
{
emit ItemReceived();
// It is important to change the state first because
// otherwise, the contracts called using `send` below
// can call in again here.
state = State.Inactive;
// NOTE: This actually allows both the buyer and the seller to
// block the refund - the withdraw pattern should be used.
buyer.transfer(value);
seller.transfer(address(this).balance);
}
}
Micropayment Channel¶
このセクションではペイメントチャンネルの実行をどうやって行うかを学びます。ここでは同じ人の間で繰り返し行われるEtherの送金を安全、高速かつトランザクションの手数料なしに行うために暗号化された署名を使います。 この例では、どの様に署名、検証し、そしてどの様にペイメントチャンネルをセットアップするかを理解する必要があります。
Creating and verifying signatures¶
アリスがボブにある量のEtherを送りたいという状況を想像してください。
アリスは暗号学的に署名されたメッセージをオフチェーン(例えばEメール)でボブに送る必要があります。これはチェックを書く時に似ています。
アリスとボブはトランザクションを許可するために署名を使います。これはEthereum上のスマートコントラクトで可能です。 アリスはEtherを送るシンプルなスマートコントラクトを作りました。しかし、その送金を始めるためのファンクションの呼び出しを自分で行う代わりに、ボブにそれを行わせ、トランザクションの手数料を払わせます。
そのコントラクトは次の様に動作します:
- アリスは最終的に行われる支払いをカバーするのに十分な量のEtherを付与した上で
ReceiverPays
コントラクトをデプロイします。- アリスは秘密鍵による署名により支払いを許可します。
- アリスは暗号学的に署名されたメッセージをボブに送ります。そのメッセージは隠される必要はありません(後で説明します)し、この送信メカニズムは重要ではありません。
- コントラクトに署名されたメッセージを渡すことでボブは支払いを要求します。コントラクトはメッセージの有効性を確認の上、コントラクト上の資金を解放します。
Creating the signature¶
アリスはトランザクションに署名するのにEthereumネットワークに繋げる必要はありません。このプロセスは完全にオフラインで行われます。このチュートリアルでは web3.js と MetaMask を使ってブラウザ上で署名を行います。さらに EIP-762 の中のメソッドで、他のセキュリティに関する恩恵を色々得られるメソッドを使用します。
/// Hashing first makes things easier
var hash = web3.utils.sha3("message to sign");
web3.eth.personal.sign(hash, web3.eth.defaultAccount, function () { console.log("Signed"); });
注釈
web3.eth.personal.sign
は署名されたデータの長さを表しています。最初にハッシュ化されているため、メッセージは常に32バイトの長さで、この長さの接頭辞は常に同じです。
What to Sign¶
支払いを行うコントラクトでは署名されたメッセージは下記を含んでいる必要があります。
- 受領者のアドレス
- 送金額
- リプレイアタックに対する防御策
リプレイアタックは署名されたメッセージが承認を要求するのに再び使われることです。これを回避するためにEthereumのトランザクションと同じ様に、アカウントから送信されたトランザクションの数、いわゆるnonceを使用します。スマートコントラクトはnonceが何度も使われていないか確認します。
別のタイプのリプレイアタックはあるオーナーが支払いを行い、その後コントラクトを破棄する ReceiverPays
というスマートコントラクトをデプロイした時に起こり得ます。その後、オーナーが再び RecipientPays
をデプロイする時、その新しいコントラクトは前のデプロイでのnonceを知らないので、攻撃者は古いメッセージを再使用することができます。
アリスはメッセージの中にコントラクトのアドレスを含めることによりこの攻撃を防ぐことができます。さらにこの中ではコントラクトのアドレスを含んだメッセージだけが許可されます。このセクションの最後にあるコントラクトの中の claimPayment()
ファンクションの中の最初の2行でこの例を確認できます。
Packing arguments¶
署名されたメッセージの中にどんな情報を含める必要があるか分かったので、メッセージをまとめ、ハッシュ化し、署名する準備ができました。
シンプルにするためにこのデータを連結させます。
ethereumjs-abi ライブラリは soliditySHA3
というファンクションを持っています。これは abi.encodePacked
を使ってエンコードされた引数に適用されたSolidityの keccak256
と同じ振る舞いをします。以下は ReceiverPays
で適切な署名を作成するJavaScriptのファンクションの例です。
// recipient is the address that should be paid.
// amount, in wei, specifies how much ether should be sent.
// nonce can be any unique number to prevent replay attacks
// contractAddress is used to prevent cross-contract replay attacks
function signPayment(recipient, amount, nonce, contractAddress, callback) {
var hash = "0x" + abi.soliditySHA3(
["address", "uint256", "uint256", "address"],
[recipient, amount, nonce, contractAddress]
).toString("hex");
web3.eth.personal.sign(hash, web3.eth.defaultAccount, callback);
}
Recovering the Message Signer in Solidity¶
一般的にECDSA署名は r
と s
という2つのパラメータで構成されています。Ethereum上の署名は3つ目のパラメータ v
を含んでいます。これは署名のどのアカウントの秘密鍵が使われたか、トランザクションの送信者が誰か検証するのに使われます。
Solidityは r
、s
そして v
パラメータと一緒にメッセージを受け入れるビルトインファンクションの ecrecover を持っています。さらにこのファンクションはメッセージに署名したアドレスを返します。
Extracting the Signature Parameters¶
web3.jsでされた署名は r
、s
、v
が連結されたものなので、最初のステップはこれをそれぞれに分けることです。これはクライアント側でもできますが、スマートコントラクト内ですれば1つのパラメータだけで済みます。バイト配列を要素ごとに分けるのは見た目があまり良くないので、splitSignature
ファンクション(セクションの最後のフルコントラクトの3番目のファンクション)内でこの操作を行うために inline assembly を使用します。
Computing the Message Hash¶
スマートコントラクトはどのパラメータがサインされたか知る必要あるので、スマートコントラクトはパラメータをメッセージから再作成し、それを署名の認証に使わなければなりません。prefixed
と recoverSigner
ファンクションは claimPayment
ファンクションの中でこれを行います。
The full contract¶
pragma solidity >=0.4.24 <0.6.0;
contract ReceiverPays {
address owner = msg.sender;
mapping(uint256 => bool) usedNonces;
constructor() public payable {}
function claimPayment(uint256 amount, uint256 nonce, bytes memory signature) public {
require(!usedNonces[nonce]);
usedNonces[nonce] = true;
// this recreates the message that was signed on the client
bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, amount, nonce, this)));
require(recoverSigner(message, signature) == owner);
msg.sender.transfer(amount);
}
/// destroy the contract and reclaim the leftover funds.
function kill() public {
require(msg.sender == owner);
selfdestruct(msg.sender);
}
/// signature methods.
function splitSignature(bytes memory sig)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
require(sig.length == 65);
assembly {
// first 32 bytes, after the length prefix.
r := mload(add(sig, 32))
// second 32 bytes.
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes).
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig)
internal
pure
returns (address)
{
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);
return ecrecover(message, v, r, s);
}
/// builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
Writing a Simple Payment Channel¶
アリスは今、シンプルですが完全なペイメントチャンネルを作っています。ペイメントチャンネルは繰り返されるEtherのやり取りを安全、即時、かつトランザクション手数料なしで行うため、暗号学的な署名を使用しています。アリスとボブによるシンプルな間接的ペイメントチャンネルを考えてみましょう。
What is a Payment Channel?¶
ペイメントチャンネルは参加者にトランザクションを使用しないで何度もEtherのやり取りをできる様にしています。つまりトランザクションに関わる遅れや手数料が発生しないということです。
- アリスはあるコントラクトにEtherでお金を入れました。これでペイメントチャンネルが"開きます"。
- アリスは何Etherが受領者に受け渡されるか書いてあるメッセージに署名しました。このステップは支払いごとに繰り返されます。
- ボブは支払われたEtherを引き出し、残りを送金者に返しペイメントチャンネルを"閉じました"。
注釈
ステップ1と3だけトランザクションが必要で、ステップ2では送金者が受領者にオフチェーンの方法(例えばEmail)で署名されたメッセージを送っているということです。つまりたった2つのトランザクションだけでいくらでも送金が行えるということです。
スマートコントラクトがエスクロー(第三者信託)としてEtherを扱い、そして有効に署名されたメッセージを引き受けているため、ボブはファンドされたお金を受け取れることが保証されています。スマートコントラクトは二人のペイメントチャンネルのタイムアウトを行うこともできるので、アリスは受領者がチャンネルのクローズを拒否してもお金が戻ってくることが保証されています。どのくらいペイメントチャンネルを開いておくかは参加者が決めることができます。短い期間のトランザクションでは例えばインターネットカフェで分ごとに課金される仕組みであったり、もっと長いもので言えば、時給で働く従業員への支払いに使えますし、ペイメントチャンネルは何ヶ月、何年もオープンにしておくことができます。
Opening the Payment Channel¶
ペイメントチャンネルを開くためにアリスはスマートコントラクトをデプロイしました。そのスマートコントラクトにはエスクローされるEtherを渡し、受領者とチャンネルの最大存続期間を決めました。これはこのセクションの最後にあるコントラストの中の SimplePaymentChannel
ファンクションに入っています。
Making Payments¶
アリスはボブに署名されたメッセージを送ることで支払いを行います。このステップは完全にEthereumネットワークの外側で行われます。 メッセージは送信者により暗号化された署名が行われ、受領者に直接送られます。
- それぞれのメッセージは以下の情報を含んでいます。
- スマートコントラクトのアドレス(クロスコントラクト攻撃を防ぐため)
- 現状受領者が受け取っているEtherの総額
ペイメントチャンネルは幾度と行われる送金の最後に一度だけクローズされます。 このため送られたメッセージの内、1つだけが履行されます。これが各マイクロペイメントの額ではなく累積額をメッセージにのせている理由です。最新のメッセージが最高額が書いてあるので、受領者は自然にそのメッセージを履行します。 スマートコントラクトは1つのメッセージだけを受け入れるため、メッセージごとのナンスはもう必要ありません。意図していた以外の他のチャンネルによって使われない様に、このスマートコントラクトのアドレスは使われたままです。
以下にメッセージに暗号学的に署名した前回のセクションから修正したJavaScriptを示します。
function constructPaymentMessage(contractAddress, amount) {
return abi.soliditySHA3(
["address", "uint256"],
[contractAddress, amount]
);
}
function signMessage(message, callback) {
web3.eth.personal.sign(
"0x" + message.toString("hex"),
web3.eth.defaultAccount,
callback
);
}
// contractAddress is used to prevent cross-contract replay attacks.
// amount, in wei, specifies how much Ether should be sent.
function signPayment(contractAddress, amount, callback) {
var message = constructPaymentMessage(contractAddress, amount);
signMessage(message, callback);
}
Closing the Payment Channel¶
ボブがチャンネルにあるお金を受け取る準備ができた時、スマートコントラクト内の close
ファンクションを呼び出し、チャンネルをクローズする時間です。
チャンネルを閉じるときに、受領者に彼らがチャンネルに渡したEtherが支払われ、コントラクトは破棄されます。残っているEtherはアリスに返却されます。チャンネルを閉じるために、ボブはアリスによってサインされたメッセージを提供する必要があります。
スマートコントラクトはそのメッセージに送信者からの有効な署名がなされているか検証しなければなりません。この検証プロセスの目的は受領者が使うプロセスと同じです。このセクションの最後にあるSolidityの isValidSignature
と recoverSigner
ファンクションは ReceiverPays
コントラクトから借りてきたファンクションと共に、これらに対応する前セクションのJavascriptのファンクションと同様な動きをします。
一番最近のペイメントのメッセージを送ったペイメントチャンネルの受領者のみが close
ファンクションを呼ぶことができます。なぜならそのメッセージが一番高い合計の金額を持っているからです。もし送信者がこのファンクションを呼ぶ権限を持っていると、その送信者が低い総額のメッセージを作って、受領者が受け取るべき金額を改ざんできてしまいます。
そのファンクションは署名されたメッセージが与えられたパラメータと合っているか検証します。全ての検証が通ったら、受領者は取り分のEtherを受け取り、送信者が selfdestruct
を通じて残りを受け取ります。フルコントラクト内で close
ファンクションは見ることができます。
Channel Expiration¶
ボブはペイメントチャンネルをいつでも閉じることができます。しかしチャンネルが閉じられなかった場合、アリスは第三者預託されたお金を回収する方法が必要になります。コントラクトのデプロイ時にコントラクトの失効期日がセットされます。その日になると、アリスはお金を回収するために claimTimeout
を呼ぶことができます。claimTimeout
ファンクションはフルコントラクト内で確認できます。
The full contract¶
pragma solidity >=0.4.24 <0.6.0;
contract SimplePaymentChannel {
address payable public sender; // The account sending payments.
address payable public recipient; // The account receiving the payments.
uint256 public expiration; // Timeout in case the recipient never closes.
constructor (address payable _recipient, uint256 duration)
public
payable
{
sender = msg.sender;
recipient = _recipient;
expiration = now + duration;
}
function isValidSignature(uint256 amount, bytes memory signature)
internal
view
returns (bool)
{
bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount)));
// check that the signature is from the payment sender
return recoverSigner(message, signature) == sender;
}
/// the recipient can close the channel at any time by presenting a
/// signed amount from the sender. the recipient will be sent that amount,
/// and the remainder will go back to the sender
function close(uint256 amount, bytes memory signature) public {
require(msg.sender == recipient);
require(isValidSignature(amount, signature));
recipient.transfer(amount);
selfdestruct(sender);
}
/// the sender can extend the expiration at any time
function extend(uint256 newExpiration) public {
require(msg.sender == sender);
require(newExpiration > expiration);
expiration = newExpiration;
}
/// if the timeout is reached without the recipient closing the channel,
/// then the Ether is released back to the sender.
function claimTimeout() public {
require(now >= expiration);
selfdestruct(sender);
}
/// All functions below this are just taken from the chapter
/// 'creating and verifying signatures' chapter.
function splitSignature(bytes memory sig)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
require(sig.length == 65);
assembly {
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig)
internal
pure
returns (address)
{
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);
return ecrecover(message, v, r, s);
}
/// builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
注釈
splitSignature
ファンクションは全てのセキュリティチェックを使いません。本当の実行時にはもっと厳しくテストされたopenzepplin's version の様なライブラリを使用するべきです。
Verifying Payments¶
前のセクションとは違い、ペイメントチャンネル内のメッセージはすぐには履行されません。受領者が最新のメッセージを確認し続け、ペイメントチャンネルを閉じるときにそのメッセージを履行します。つまり受領者が各メッセージの検証をすることが重要ということです。 そうしないと、受領者が最終的にお金を受け取れる保証がされなくなります。
受領者は下記のプロセスで各メッセージを検証すべきです。
- メッセージの中のコントラクトアドレスがペイメントチャンネルと合っているか検証してください。
- 新しい総額が予定していたものと同じか検証してください。
- 新しい総額が第三者預託されたEtherを超えていないか検証してください。
- 署名が有効か、そしてペイメントチャンネルの送信者からのものか検証してください。
この検証を記載するために ethereumjs-util ライブラリを使用します。最終ステップは色々な方法で行うことができますが、ここではJavaScriptを使用します。次のコードは上記の JavaScriptコード の署名から constructMessage ファンクションを借りています。
// this mimics the prefixing behavior of the eth_sign JSON-RPC method.
function prefixed(hash) {
return ethereumjs.ABI.soliditySHA3(
["string", "bytes32"],
["\x19Ethereum Signed Message:\n32", hash]
);
}
function recoverSigner(message, signature) {
var split = ethereumjs.Util.fromRpcSig(signature);
var publicKey = ethereumjs.Util.ecrecover(message, split.v, split.r, split.s);
var signer = ethereumjs.Util.pubToAddress(publicKey).toString("hex");
return signer;
}
function isValidSignature(contractAddress, amount, signature, expectedSigner) {
var message = prefixed(constructPaymentMessage(contractAddress, amount));
var signer = recoverSigner(message, signature);
return signer.toLowerCase() ==
ethereumjs.Util.stripHexPrefix(expectedSigner).toLowerCase();
}
Solidity in Depth¶
このセクションではSolidityに関してあなたが知りたいことを全て説明しているはずです。 もし何かが抜けていたら Gitter で連絡下さい。もしくは Github でプルリクエストを送ってください。
Layout of a Solidity Source File¶
ソースファイルは任意の数の コントラクトの定義、import の指示、pragmaの指示 を含むことができます。
Pragmas¶
pragma
というキーワードは特定のコンパイラの機能を使用可能にするもしくはコンパイラのバージョンをチェックするのに使用することができます。pragma指示は常にローカルのソースファイルにあるので、プロジェクト全体で使用可能にしたい場合にはpragmaを全てのファイルに追加する必要があります。もし他のファイルを インポート してもそのファイルから得たpragmaは自動的にはインポートしているファイルには適用されません。
Version Pragma¶
互換性がない可能性がある未来のコンパイラでのコンパイルを避けるために、ソースファイルはいわゆるバージョンPragmaの注記をつけることができます(そしてつけるべきです)。私たちはこの様な変更を最小限にする努力をしていますし、特にセマンティクス上の変更がシンタックスの変更も必要とする様な変更は通知しています。しかし、もちろん常にできる訳ではないので、少なくともブレーキングチェンジを含む様な大きな変更があるときは変更のログを見ることは良いことです。この様な大きな変更がある場合にはバージョンは 0.x.0
もしくは x.0.0
の様になります。
バージョンPragmaは下記の様に使用されます:
pragma solidity ^0.5.2;
この様なソースファイルはバージョン0.5.2より低いバージョンのコンパイラでコンパイルすることはありませんし、0.6.0以上のコンパイラでコンパイルすることもありません(後者の条件は ^
を使うことで追加しています)。このアイデアの根本はバージョン 0.6.0
まで大きな変更(ブレーキングチェンジ)がなく、コードが意図した通りにコンパイルされるということを保証されるというものです。コンパイラのバージョンを固定しないので、bugfixされたバージョンも使用可能です。
もっと複雑なコンパイラのバージョンのルールを指定することもできます。npm を使った方法に準拠します。
注釈
version pragmaを使ってもコンパイラのバージョンを変えることはできません。また、コンパイラの機能を有効にしたり無効にしたりもできません。ただコンパイラにpragmaで要求されたバージョンと合っているかチェックさせるだけです。合っていなければコンパイラはエラーを出します。
Experimental Pragma¶
2つ目のpragmaは実験的なpragmaです。これはコンパイラや言語のまだデフォルトで有効になっていない機能を有効にするのに使うことができます。下記の実験的なpragmaは現在サポートされています。
ABIEncoderV2¶
新しいABI encoderは任意にネストされた配列と構造体をエンコード、デコードできます。これはあまり最適化されていないコードを生成します。(この部分のオプティマイザは未だ開発中です。)そして、古いエンコーダほどテストがされていません。pragma experimental ABIEncoderV2;
を使うことでこれを有効化できます。
SMTChecker¶
このコンポーネントはSolidityのコンパイラが組まれているときに有効でなければならない。そのためSolidityのバイナリでは利用できない。build instructions はどの様にこのオプションを有効にしているか説明しています。 これはほとんどのバージョンのUbuntu PPAのリリースのために有効化されるが、solc-js、Dockerイメージ、 Windowsバイナリやstatically-built Linuxバイナリのためではありません。
もし pragma experimental SMTChecker;
を使うなら、SMT solverにクエリすることで取得される追加の安全警告を受け取ります。このコンポーネントはまだ全てのSolidityの機能をサポートしていないので、たくさんの警告を発する可能性が高いです。もしサポートされていない機能がレポートされた場合でも、その分析は完璧ではないかもしれません。
Importing other Source Files¶
Syntax and Semantics¶
"default export"はありませんが、SolidityはJavascript(ES6)の様なimportの宣言をサポートしています。
グローバルのレベルで、下記の様なimportの宣言ができます。
import "filename";
この宣言は全てのグローバルな記号を"filename" (とそこにインポートされた記号)から現在のグローバルスコープ(ES6とは違いますがSolidityに後方互換性があります)にインポートします。 この単純な使い方は推奨されません。なぜなら、名前空間を予期せぬ方法で汚してしまうからです。もし"filename"内でトップレベルのアイテムを追加したら、"filename"からインポートした全てのファイルで自動的にそのアイテムが現れます。特定の記号だけを明示的にインポートした方が良いです。
下記の例では全ての要素が "filename"
から来たグローバルな記号である新しいグローバルな記号 symbolName
が作られます。
import * as symbolName from "filename";
もし名前の重複があった場合には、インポートの際に名前を変えることができます。次のコードでは新しいグローバルな記号 alias
と symbol2
を作ります。それぞれ "filename"
の中の symbol1
と symbol2
を参照しています。
import {symbol1 as alias, symbol2} from "filename";
次の例はES6の一部ではありませんが、おそらく便利でしょう。
import "filename" as symbolName;
これは import * as symbolName from "filename";
と等価です。
注釈
もし import "filename.sol" as moduleName; を使うのであれば、"filename.sol" の中から moduleName.C として C と呼ばれるコントラクトにアクセスしてください。C は直接使わないでください。
Paths¶
上では、filename
は常にディレクトリのセパレータとしての /
、現在のディレクトリとしての .
、親ディレクトリとしての ..
と一緒にパスとして使われていました。.
と ..
は /
の後に続かなければ、現在もしくは親ディレクトリとしては扱われません。全てのパスは .
もしくは ..
で始まらなければ絶対パスとして扱われます。
現在のファイルと同じディレクトリにあるファイル x
をインポートするためには、import "./x" as x;
を使ってください。
実際にどの様にパスが読み込まれるかはコンパイラによります(下記参照)。一般的に、ディレクトリ構造はローカルに限定されません。例えばipfs、httpやgitを通じて得たリソースを指定することも可能です。
注釈
常に import "./filename.sol";
の様な相対パスを使ってください。また、パスを指定するのに ..
を使うのは避けてください。後のケースではおそらくグローバルパスを使い、下記で説明するリマッピングをセットアップするのが良いでしょう。
Use in Actual Compilers¶
コンパイラを呼び出す時に、パスの最初の要素とパスのプレフィックスのリマッピングをどの様に指定するか決めることができます。例えば、あるリマッピングをセットアップしたら、仮のディレクトリ github.com/ethereum/dapp-bin/library
からインポートしたもの全てが実際にはローカルのディレクトリ /usr/local/dapp-bin/library
から読み込まれているといった様なことができます。
もし複数のリマッピングを使うと、一番長いキーをもつリマッピングが最初に適用されます。
空のプレフィックスは使えません。リマッピングはコンテキストに依存します。そのため、例えば同じ名前の異なるバージョンのライブラリをインポートするためにパッケージを設計できます。
solc:
solc(コマンドラインコンパイラ)に、context:prefix=target
属性としてパスのリマッピングを渡します。context:
と =target
のパートはオプションです(この場合 target
が prefix
のデフォルトとなります)。通常ファイルの全てのリマッピング値はコンパイルされます(それらの依存関係も含めて)。
このメカニズムは後方互換性をもち(ファイル名に =
もしくは :
を含んでいない限り)、そのためブレーキングチェンジにはなりません。prefix
で始まるファイルをインポートする context
ディレクトリの中もしくは以下にある全てのファイルは prefix
を target
に変更することでリダイレクトされます。
例えば、もし github.com/ethereum/dapp-bin/
をローカルの /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
もっと複雑な例として、もし /usr/local/dapp-bin_old
を参照している古いバージョンのdapp-binを使っているモジュールを使っているとしたら、次のコードを使用することができます。
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
は特定のディレクトリからのファイルを含めるのを許可するだけです。そのファイルははっきりと明示されたソースファイルの1つ、もしくはリマッピングのターゲットのディレクトリ(もしくはサブディレクトリ)の中にある必要があります。もし直接的に含めたい場合にはリマッピングに /=/
を追加して下さい。
もし有効なファイルを参照する複数のリマッピングがあった場合には、一番長い共通のプレフィックスがついているリマッピングが選択されます。
Remix:
Remix は自動的にGithubにリマッピングし、自動的にネットワークを通じてファイルを引っ張ってきます。上記の様な繰り返し可能なマッピングをインポートできます。例えば、
- ::
- import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;
Remixはおそらく将来的に他のソースコードプロバイダを追加するかもしれません。
Comments¶
1行コメント(//
)と複数行コメント(/*...*/
)が使用可能です。
// これは1行コメントです。
/*
これは複数行
コメントです。
*/
注釈
1行コメントはutf8エンコードにおいてどのunicode方式のラインブレーク(LF, VF, FF, CR, NEL, LS, PS)でも終了します。ラインブレークはコメントの後でもソースコードの一部となっているため、ascii記号(NEL, LS, PS)でない場合にはパーサーエラーを起こします。
さらに別のタイプのnatspecコメントというコメントがあります。これは style guide で詳細を確認できます。これはトリプルスラッシュ(///
)かダブルアスタリスクブロック(/** ... */
)で書かれ、ファンクションの宣言の直前に書かれます。
ファンクションを付記したり、形式を検証するための条件を注記したり、ユーザーがファンクションを実行する時に表示される confirmation text を追加するために、このコメントの中の Doxygen-styleタグを使うことができます。
次の例の中ではコントラクトのタイトル、2つのファンクションのパラメータの説明、2つの返り値が示されています。
pragma solidity >=0.4.0 <0.6.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) public pure returns (uint s, uint p) {
s = w * h;
p = 2 * (w + h);
}
}
Structure of a Contract¶
Solidityのコントラクトはオブジェクト志向の言語のクラスに似ています。各コントラクトは State Variables、Functions、Function Modifiers、Events、Struct Types、Enum Types の宣言を含んでいます。更に、コントラクトは他のコントラクトを継承できます。
ライブラリ と インターフェース と呼ばれる特別な種類のコントラクトもあります。
コントラクト に関するセクションはより詳細な説明があります。このセクションは簡単な全体像です。
State Variables¶
状態変数とは値が永久的にコントラクトのストレージに保存される値です。
pragma solidity >=0.4.0 <0.6.0;
contract SimpleStorage {
uint storedData; // State variable
// ...
}
有効な状態変数のタイプは Types を、可視性の実行可能な選択肢については Visibility and Getters を参照ください。
Functions¶
ファンクションはコントラクト内にある実行可能なコードの一式です。
pragma solidity >=0.4.0 <0.6.0;
contract SimpleAuction {
function bid() public payable { // Function
// ...
}
}
Function Calls は内部でも外部でも行うことができ、他のコントラクトに対して異なったレベルの visibility を持ちます。パラメータや値を受け渡しするために、Functions は parameters and return variables を受け入れます。
Function Modifiers¶
ファンクションModifierは宣言的な方法でファンクションのセマンティクスを修正することができます(コントラクトセクションの Function Modifiers を参照ください)。
pragma solidity >=0.4.22 <0.6.0;
contract Purchase {
address public seller;
modifier onlySeller() { // Modifier
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
function abort() public view onlySeller { // Modifier usage
// ...
}
}
Events¶
イベントはEVMのロギング機能で使われる便利なインターフェースです。
pragma solidity >=0.4.21 <0.6.0;
contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount); // Event
function bid() public payable {
// ...
emit HighestBidIncreased(msg.sender, msg.value); // Triggering event
}
}
DApp内でイベントがどの様に宣言され、使用されるかはコントラクトセクションの Events を参照ください。
Types¶
Solidityは静的タイプの言語です。つまり各状態変数やローカルな変数のタイプは指定される必要があります。 Solidityはいくつかの基本的なタイプを持ち、それらは結びつき複雑なタイプを構成することができます。
加えて、オペレータを含む表現でタイプは互いに干渉することができます。変数のオペレータのクイックリファレンスは Order of Precedence of Operators を参照ください。
Value Types¶
次の型も常に値として渡されるため値型と呼ばれています。引数や割り当て時にはこれらは常にコピーされます。
Booleans¶
bool
: 取りうる値は true
と false
Operators:
!
(論理否定)&&
(論理積, "and")||
(論理和, "or")==
(等価)!=
(不等価)
||
と &&
演算子は一般的な短絡評価のルールに従います。つまり、f(x) || g(y)
という表現において、もし f(x)
が``true`` と評価された場合、たとえ副次的な作用があったとしても g(y)
は評価されません。
Integers¶
int
/ uint
: 色々なサイズの符号付と符号なし整数です。uint8
から uint256
まで8ずつ(符号なしの8から256ビットまで)と``int8`` から int256
まで上がっていきます。uint
と int
はそれぞれ uint256
と int256
のエイリアスです。
Operators:
- 比較:
<=
,<
,==
,!=
,>=
,>
(bool
で評価) - ビット演算子:
&
,|
,^
(ビット排他論理和),~
(ビット否定) - シフト演算子:
<<
(左シフト),>>
(右シフト) - 算術演算子:
+
,-
, unary-
,*
,/
,%
(modulo),**
(累乗)
警告
Solidityの整数はある範囲に制限されています。例えば、uint32
であれば 0
から最大 2**32 - 1
までです。
もし計算結果がこの範囲に収まらない場合には、切り捨てられます。この切り捨てによって起こる結果は be 知っておくべきです 。
Comparisons¶
比較の値は整数値を比較することによって得られます。
Bit operations¶
ビット演算子の計算は2の補数表現で行われます。例えば、 ~int256(0) == int256(-1)
です。
Shifts¶
シフト演算の結果は左オペランドの型となります。x << y
という表現は x * 2**y
と等価です。さらに正の整数に関しては x >> y
と x / 2**y
が等価です。負の x
に対して x >> y
は、2
のべき乗で除し、切り捨てしたもの(負の無限大で)と等価です。
負の数でシフトするとランタイム例外が投げられます。
警告
バージョン``0.5.0``の前までは、負の x
に対して右シフト x >> y
は x / 2**y
と等価でした。例えば右シフトは負の無限大での切り捨ての代わりに、0の位での切り捨てを行なっていました。
Addition, Subtraction and Multiplication¶
加算、減算、乗算は通常のセマンティクスです。
これらは2の補数表現で使用されます。つまり、例えば uint256(0) - uint256(1) == 2**256 - 1
です。安全なスマートコントラクトを作成するときには、このオーバーフローを考慮する必要があります。
T
が x
の型であるとき -x
という表現は (T(0) - x)
と等価です。つまり、x
の型が符号なし整数のときに -x
は負の数にはなりません。また、x
が負の数であれば -x
は正の数になりえます。さらに、別の2の補数表現による注意があります:
int x = -2**255;
assert(-x == x);
これが意味するのは、たとえある数字が負の数でも、それにマイナスをつけたものが正の数になるとは限らないということです。
Division¶
ある演算の出力の型は常に演算対象の型と同じなので、整数の除算の結果は整数になります。Solidityでは、除算は1の位までの概算になります。つまり、int256(-5) / int256(2) == int256(-2)
となります。
一方で、リテラル での除算は任意の精度での少数値が結果として出力されるということに注意して下さい。
注釈
ゼロでの除算はフェイルアサーションが発生します。
Modulo¶
剰余演算 a % n
は a
を n
で割ったときの 余り r
を結果として返します(q = int(a / n)
で r = a - (n * q)
です)。つまり、剰余演算の答えは左オペランドと同じ符号(もしくはゼロ)で、負の数 a
に対して a % n == -(abs(a) % n)
となります:
int256(5) % int256(2) == int256(1)
int256(5) % int256(-2) == int256(1)
int256(-5) % int256(2) == int256(-1)
int256(-5) % int256(-2) == int256(-1)
注釈
0での剰余演算はフェイルアサーションが発生します。
Exponentiation¶
指数演算は符号なしの型でのみ使用可能です。使っている型が指数演算の結果を包括するのに、また将来的に起こりうるラッピングに対して十分な大きさであることを確認してください。
注釈
EVMでは 0**0
は 1
と定義されています。
Fixed Point Numbers¶
警告
固定小数点はまだSolidityでは完全にサポートされていません。宣言はできますが、値を割り当てたりはできません。
fixed
/ ufixed
: いくつかのサイズがある符号付、符号なし固定小数点です。ufixedMxN
と fixedMxN
で M
はその型で取れるビットの数で、N
は、何桁の10進数少数点が取れるかを表しています。M
は8で割り切れる数で、8から256ビットまでとれます。N
は0から80までとることができます。ufixed
と fixed
はそれぞれ ufixed128x18
と fixed128x18
のエイリアスです。
Operators:
- Comparisons:
<=
,<
,==
,!=
,>=
,>
(evaluate tobool
) - Arithmetic operators:
+
,-
, unary-
,*
,/
,%
(modulo)
注釈
浮動小数と固定小数の主な違いですが、前者では整数部分と小数部分(多くの言語では float
と double
です。より詳細な情報はIEEE 754で確認してください)の桁数がフレキシブルで、後者では厳密に決められていることです。一般的に、浮動小数点数はほぼ全てのスペースをその数を表すのに使用し、少しのビットで小数点の長さを表します。
Address¶
アドレス型は広義的には同じである2つの種類があります:
address
: 20バイトの値 (Ethereumアドレスの大きさ)です。address payable
:address
と同じですが、追加のメンバtransfer
とsend
が使えます。
この特徴が意味するのは、address payable
にはEtherを送ることができますが、ただの address
にはできません。
Type conversions:
address payable
から address
への暗黙的な変換は可能ですが、address
から address payable
にはできません(唯一 uint160
を中継することで変換可能です)。
アドレスリテラル は暗黙的に address payable
に変換可能です。
address
への、もしくは address
からの明示的な変換は整数、整数リテラル、bytes20
、コントラクト型で可能ですが下記の注意事項を参照ください:
address payable(x)
という形での変換はできません。代わりに、address(x)
という変換結果が address payable
もしくは、もし x
が整数もしくは固定サイズのバイト型であれば、リテラルかpayableのフォールバックファンクションを持つコントラクトになります。
もし x
がpayableのフォールバックファンクションを持たないコントラクトであれば、address(x)
は address
になります。
外部のファンクションの署名では、address
は address
型、address payable
型両方で使用されます。
注釈
おそらく、address
と address payable
の違いを気にする必要はあまりなく、どこでも address
を使うことになるでしょう。例えば、もし、withdrawal pattern を使うと、address payable
である msg.sender
で transfer
を使うので、address
としてアドレスを保存できます(するべきです)。
Operators:
<=
,<
,==
,!=
,>=
and>
警告
もし address
型よりも大きなサイズの型、例えば、bytes32
、から変換しようとすると、address
は切り詰められます。
変換時の曖昧さを減らすためにバージョン0.4.24からは変換時にコンパイラは切り捨てを明示的にすることを要求します。例えば、0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC
です。
0x111122223333444455556666777788889999aAaa
となる address(uint160(bytes20(b)))
もしくは、0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc
となる address(uint160(uint256(b)))
が使えます。
注釈
address
と address payable
の違いはバージョン0.5.0で導入されました。
また、コントラクトはアドレス型からは生成されませんが、もしpayableのフォールバックファンクションを持っていれば、address
もしくは address payable
に明示的に変換できるという機能がバージョン0.5.0から導入されました。
Members of Addresses¶
アドレス型の全てのメンバのクイックリファレンスは Members of Address Types を参照ください。
balance
andtransfer
balance
プロパティであるアドレスのバランスを確認できます。また、transfer
でpayableなアドレスにEther(単位はwei)を送ることができます:
address payable x = address(0x123);
address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
現在のコントラクトのバランスが十分大きくないか、受け取り側のアカウントでEtherの送金が拒否された場合、``transfer``ファンクションは失敗し、送金前の状態に戻ります。
注釈
もし x
がコントラクトアドレスだった場合、そのコード(具体的には、もしあれば Fallback Function)は transfer
と一緒に実行されます(これはEVMの特徴で、止めることはできません)。もしこの実行時にガス不足になったり、他の理由でフェイルした場合は、Etherの送金はキャンセル、元の状態に戻り、現在のコントラクトは例外と共にストップします。
send
Sendは transfer
の低レベルバージョンです。もし実行が失敗したら、現在のコントラクトは例外と共にストップしない代わりに、send
が false
を返します。
警告
send
を使うといくつかの危険が伴います:
送金はコールスタックの深さが1024でフェイルします(これは常に呼び出し元によって行われます)。そして、もし受領者がガスを使い切ってもフェイルします。そのため、安全なEtherの送金のために、常に send
の返り値を確認する、もしくは transfer
を使ってください。さらに良いのは:
受領者がお金を引き出す時の様式を使用することです。
call
,delegatecall
andstaticcall
ABIに従わないコントラクトと繋げるために、もしくはエンコードに対してもっと直接的なコントロールを得るために、call
、delegatecall
、staticcall
ファンクションを使うことができます。
これらは全て一つの bytes memory
パラメータを引数とし、(bool
で)成否と bytes memory
の返ってきたデータを返します。
abi.encode
、abi.encodePacked
、abi.encodeWithSelector
、abi.encodeWithSignature
は体系的なデータをエンコードするために使用することができます。
Example:
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);
警告
これら全てのファンクションは低級のファンクションで、使う際には注意が必要です。特に、未知のコントラクトは悪意を持っている可能性があり、もしそのコントラクトを呼び出すと、あなたのコントラクトに次々とコールバックを投げるコントラクトにコントロールを渡してしまうかもしれません。そのため、その呼び出しが返ってきたときに、状態変数の変化に対して準備をしておいてください。他のコントラクトと繋がる一般的な方法はコントラクトオブジェクト(x.f()
)上でファンクションを呼び出すことです。
注釈
以前のバージョンではこれらのファンクションで任意の引数を取ることができ、bytes4
型の第一引数を異なる方法で処理することができました。そのようなエッジケースはバージョン0.5.0で削除されました。
供給されたガスを .gas()
modifierで調整することができます:
address(nameReg).call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName"));
同様に、供給されたEtherの値もコントロールすることができます:
address(nameReg).call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
最後に、これらのmodifierは結合させることができます。順番は関係ありません:
address(nameReg).call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
似たような方法で、delegatecall
は使用されます: 違いは与えられたアドレスのコードだけ使われ、他の要素(storage、balance等)は現在のコントラクトから使われます。delegatecall
の目的は別のコントラクトに保存されているライブラリを使用することです。ユーザはstorageの構造がどちらのコントラクトでもdelegatecallを使用するのに適切であることを確認しなければいけません。
注釈
homestead以前は、callcode
という制限のある変形型のみが使用可能でしたが、オリジナルの msg.sender
と msg.value
にアクセス不可でした。このファンクションはバージョン0.5.0で削除されました。
Byzantiumから staticcall
も使うことができます。基本的には call
と同じですが、もし呼ばれたファンクションがステートを変更したらリバートします。
call
、delegatecall
、staticcall
の3つのファンクションは全てとても低級のファンクションで、Solidityの型安全性を破るため、最終手段 として使用してください。
.gas()
オプションは全てのメソッドで使用可能ですが、.value()
は delegatecall
ではサポートされません。
注釈
全てのコントラクトは address
に変換できるため、address(this).balance
で現在のコントラクトにそのバランスをクエリすることができます。
Contract Types¶
全ての contract は自分自身の型を定義します。
あるコントラクトからそのコントラクトが継承しているコントラクトへ暗黙的に変換することができます。
コントラクトは他のコントラクト型から、もしくは他のコントラクト型へ明示的に変換することができます。さらに address
型への変換も可能です。
コントラクト型がpayableのフォールバックファンクションを持っている時のみ、address payable
型から、もしくは address payable
型への明示的な変換が可能です。
その変換は address(x)
では行われますが、address payable(x)
では行われません。詳細な情報は address type を参照ください。
注釈
バージョン0.5.0以前では、コントラクトは直接アドレス型から得られており、address
と address payable
に違いはありませんでした。
もしコントラクト型(MyContract c)のローカル変数を宣言した場合、そのコントラクト上でファンクションを呼び出すことができます。同じコントラクト型からその変数を割り当てる様にして下さい。
コントラクトのインスタンスも作成可能です(つまり新しくそのコントラクトが作られるということです)。詳細は 'Contracts via new' を参照ください。
コントラクトのデータ表現は address
型のデータ表現と同じで、この型は ABI でも使われています。
コントラクト型はどんな演算子もサポートしません。
コントラクト型のメンバはpublicの状態変数を含んだそのコントラクトのexternalのファンクションです。
あるコントラクト C
に対して、そのコントラクトの type information にアクセスするために type(C)
を使うことができます。
Fixed-size byte arrays¶
値型である bytes1
、bytes2
、bytes3
... bytes32
は1から32までバイト列を保持しています。
byte
は byte1
のエイリアスです。
Operators:
- Comparisons:
<=
,<
,==
,!=
,>=
,>
(bool
を返します) - Bit operators:
&
,|
,^
(bitwise exclusive or),~
(bitwise negation) - Shift operators:
<<
(left shift),>>
(right shift) - Index access: If
x
is of typebytesI
, thenx[k]
for0 <= k < I
returns thek
th byte (read-only). - 比較:
<=
,<
,==
,!=
,>=
,>
(bool
で評価) - ビット演算子:
&
,|
,^
(ビット排他論理和),~
(ビット否定) - シフト演算子:
<<
(左シフト),>>
(右シフト) - インデックスアクセス: もし
x
がbytesI
型なら0 <= k < I
の元でx[k]
はk
番目のバイトを返します(読み取り専用)。
どれだけのビット数をシフトさせるか決める右オペランドがどの整数型でもシフト演算子は動作します(結果は左オペランドの型で返ります)。 負の数でのシフトはランタイムの例外を生成します。
Members:
.length
は固定長さのバイト配列を返します(読み取り専用)。
注釈
byte[]
はバイトの配列ですがパディングのため、各要素の間の31バイトを無駄にしています(storage以外)。代わりに、bytes
を使用する方が良いでしょう。
Dynamically-sized byte array¶
Address Literals¶
アドレスのチェックサムをパスする16進数のリテラル、例えば 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
は address payable
型です。
39から41文字で、チェックサムにパスしない16進数リテラルは警告を発し、通常の有理数リテラルとして扱われます。
注釈
The mixed-case address checksum format is defined in EIP-55.
Rational and Integer Literals¶
整数リテラルは0から9までの数字から形成され、10進数として認識されます。例えば、69
は六十九のことです。
10進数の小数リテラルは .
を使って形成され、少なくとも1文字片側に数字があります。例えば、1.
、.1
、1.3
です。
指数表記もサポートされています。基数は小数を取れますが、指数はできません。
例えば、2e10
、-2e10
、2e-10
、``2.5e1``です。
アンダースコアは数字リテラルの読みやすさを改善するために数字を分けるのに使用することができます。
例えば、10進数 123_000
、16進数の 0x2eff_abde
、10進数の指数表記 1_2e345_678
は全て有効です。
アンダースコアは2つの数字の間でのみ有効で、1つのアンダースコアしか使うことができません(2つ連続でアンダースコアを使うことはできません)。セマンティクス的な意味は何もありません。アンダースコアを含む数字リテラルでアンダースコアは無視されます。
数字リテラルは非リテラル型に(例えば非リテラル型と一緒に使うか、明示的な変換によって)変換されるまで任意の精度を持ちます。 つまり数字リテラル表現では、計算してもオーバーフローしませんし、除算では切り捨ては起きません。
例えば、中間の計算結果は機械語のサイズに収まっていませんが、(2**800 + 1) - 2**800
の結果は(uint8
型の)定数 1
になります。さらに(非整数が使われていますが)``.5 * 8`` の結果は整数 4
になります。
計算対象が整数で有る限り、整数に使える演算子は全て数字リテラルで使用することができます。 もし片方でも小数を含んでいた場合、ビット演算子は使うことはできません。また、指数部分に小数は使えません(結果が非有理数になる可能性があるため)。
注釈
Solidityは各有理数に対して数字リテラル型が使えます。整数リテラルと有理数リテラルは数字リテラル型に属します。
さらに、全ての数字リテラル表現(数字リテラルと演算子のみを含む表現)は数字リテラル型に属します。そのため、数字リテラル表現の 1 + 2
と 2 + 1
の結果である有理数の3は両方とも同じ数字リテラル型に属します。
警告
バージョン0.4.0以前では整数リテラルの除算の結果は切り捨てされていましたが、現在は有理数に変換されます。例えば、5 / 2
は 2
ではなく、2.5
です。
注釈
数字リテラル表現は非数字リテラル表現が使われたタイミングで非数字リテラル型に変換されます。
型を無視し、下記の b
に割り当てられている式の値は整数となります。a
は uint128
型であるため、2.5 + a
はある適切な型を持っていなければいけませんが、2.5
の型と uint128
型に共通した型が存在しないため、Solidityのコンパイラはこのコードを処理しません。
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
String Literals and Types¶
文字列リテラルはダブルもしくはシングルクオテーション("foo"
もしくは 'bar'
)で記述されます。これらはC言語の様な後置ゼロにはなりません。"foo"
は3バイトを表します。4バイトではありません。整数リテラルは複数の型をとりうりますが、bytes1
... bytes32
に暗黙的に変換可能です。もしサイズが合えば bytes
と string
にも変換可能です。
例えば、bytes32 samevar = "stringliteral"
では、文字列リテラルは bytes32
型に割り当てられる時に、その生のバイト構造で解釈されます。
文字列リテラルは以下のエスケープキャラクターをサポートします:
\<newline>
(escapes an actual newline)\\
(backslash)\'
(single quote)\"
(double quote)\b
(backspace)\f
(form feed)\n
(newline)\r
(carriage return)\t
(tab)\v
(vertical tab)\xNN
(hex escape, see below)\uNNNN
(unicode escape, see below)
\xNN
は16進数をとり、適切なバイトを挿入します。一方で、\uNNNN
はUnicodeのコードポイントをとり、UTF-8のシーケンスを挿入します。
以下の例の中の文字列は10バイトの長さを持ちます。 新しい行で始まり、ダブルクオート、シングルクオート、バックスラッシュと続き、(区切りなく)``abcdef`` という文字が続きます。
"\n\"\'\\abc\
def"
改行コード(例えばLF、VF、FF、CR、NEL、LS、PS)でないどんなユニコードのラインターミネータは文字列リテラルを終了させると考えられています。もし \
で処理されていないのであれば、改行は文字列リテラルを終了させるだけです。
Hexadecimal Literals¶
16進数リテラルは hex
という接頭辞をつけて、ダブルかシングルクオートで囲まれます(hex"001122FF"
)。中身は16進数の文字列でなければなりません。そしてその値は2進数表現になります。
16進数リテラルは string literals の様に振る舞い、変換に関して同じ制限を持っています。
Enums¶
列挙型はSolidityでのユーザー定義型の1つです。明示的に整数型から、もしくは整数型に変換可能ですが、暗黙的には変換できません。整数型からの明示的な変換では実行時にその値が列挙型の範囲内に収まっているかチェックし、範囲外である場合にはフェイルアサーションを起こします。 列挙型は少なくとも1つの要素が必要です。
そのデータ表現はC言語の列挙型と同じです。オプションは 0
で始まる符号なし整数値によって表されます。
pragma solidity >=0.4.16 <0.6.0;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
}
// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity. The integer type used is just
// large enough to hold all enum values, i.e. if you have more than 256 values,
// `uint16` will be used and so on.
function getChoice() public view returns (ActionChoices) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
}
Function Types¶
ファンクション型はファンクションの型です。 ファンクション型の変数はファンクションから割り当てられ、ファンクションコールにファンクションを渡す、またはファンクションコールからファンクションをリターンするためにファンクション型のパラメータは使用されます。 ファンクション型は2種類あります - internal と external ファンクションです:
現在のコントラクトの外からは実行することができないため、internalファンクションは現在のコントラクト内でのみ呼び出すことができます(具体的には、internalのライブラリファンクションや継承したファンクションも含むコード内)。internalのファンクションは、現在のコントラクト内部でファンクションを呼び出す様に、そのファンクションのエントリポイントにジャンプすることによって実行されます。
Externalファンクションはアドレスとファンクションの署名によって構成され、外部からのファンクションコールを通し、返ってきます。
ファンクションの種類は下記の様に表されます:
function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]
parameter typesと異なり、return typesは空ではいけません。もしファンクションが何も返さないのであれば、returns (<return types>)
部分は除外しなければなりません。
デフォルトでは、ファンクション型はinternalで、internal
というキーワードは削除できます。これはファンクション型でのみ可能です。コントラクト内で定義されたファンクションはデフォルトで定義されておらず、可視性を明示しなければいけません。
Conversions:
externalファンクション型の値は明示的に address
に変換可能で、そのファンクションのコントラクトのアドレスになります。
もしパラメータの型、返り値の型が同じであり、internal/externalのプロパティも同じ、さらに A
のミュータビリティの制限が B
に比べて厳しくない場合あるファンクション型 A
は 別のファンクション型 B
に暗黙的に変換可能です。特に:
pure
ファンクションはview
とnon-payable
ファンクションに変換可能view
ファンクションはnon-payable
ファンクションに変換可能payable
はnon-payable
ファンクションに変換可能
他のファンクション型間の変換はできません。
payable
と non-payable
間のルールは少し分かりづらいかもしれません。しかし、大事なことは、payable
ファンクションは0 etherの支払いを容認し、同様に non-payable
ファンクションもそれを容認することです。一方で、non-payable
はEtherの受け取りを拒否するため、non-payable
ファンクションは payable
ファンクションに変換できません。
ファンクション型の変数が初期化されていない場合、その変数を呼び出してもフェイルアサーションとなります。delete
をその変数に対して使った後に呼び出した場合も同じことが起きます。
もしexternalのファンクション型がSolidityのコンテキスト外で使用された場合、ファンクション型として扱われます。そして、それはそのファンクションの識別子とその後のアドレスを一緒に1つの bytes24
型にエンコードします。
現在のコントラクトのpublicのファンクションはinternalファンクションとしてもexternalファンクションとしても使用可能です。f
をinternalファンクションとして使用したい場合には、単純に f
を、もしexternalファンクションとして使用した場合には、this.f
を使用してください。
Members:
Public(もしくはexternal)のファンクションは selector
という特別なメンバも持っています。これは ABI function selector を返します:
pragma solidity >=0.4.16 <0.6.0;
contract Selector {
function f() public pure returns (bytes4) {
return this.f.selector;
}
}
internalのファンクション型の使用例です:
pragma solidity >=0.4.16 <0.6.0;
library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal pure returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
}
externalファンクション型の別の使用例です:
pragma solidity >=0.4.22 <0.6.0;
contract Oracle {
struct Request {
bytes data;
function(uint) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes memory data, function(uint) external callback) public {
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, uint response) public {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant oracle = Oracle(0x1234567); // known contract
uint exchangeRate;
function buySomething() public {
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(uint response) public {
require(
msg.sender == address(oracle),
"Only oracle can call this."
);
exchangeRate = response;
}
}
注釈
ラムダ式もしくはインラインファンクションの導入が予定されていますが、まだサポートされていません。
Reference Types¶
参照型の値は複数の異なった名前で修正できます。値型の変数が使用される度に、独立したコピーをとった値型と比較してみて下さい。このことから参照型は値型より気をつけて扱う必要があります。現在、構造体、配列、マッピングは参照型です。もし参照型を使用しているのであれば、値が保存されるデータ領域を常に明示する必要があります: memory
(ライフタイムはファンクションの呼び出し時のみに制限されます)、storage
(状態変数が保存されている場所)、もしくは calldata
(特別なデータロケーションで、ファンクションの引数を含み、externalのファンクションコールのパラメータでのみ使用可能です)。
データロケーションを変える値の割り当てや型変換では常に自動でコピー操作が行われる一方で、 同じデータロケーション内での値の割り当てはただコピーするだけです(いくつかの例ではstorage型で)。
Data location¶
配列、構造体 の様な全ての参照型は"data location"というそれが保存されている場所を表す追加のアノテーションを持っています。memory
, storage
and calldata
という3つのデータロケーションがあります。Calldataは外部コントラクトのファンクションの参照型のパラメータとして要求される場合にのみ有効です。Calldataは修正不可で、ファンクションの引数が保存される非永続的なエリアで、基本的にはmemoryの様に振舞います。
注釈
バージョン0.5.0以前では、データロケーションは省略可能ですが、変数の種類やファンクションの種類などによってデフォルトで異なる場所に保存されます。しかし、現在は全ての複雑な型は明示的にデータロケーションを示す必要があります。
Data location and assignment behaviour¶
データロケーションはデータの持続性だけではなく、割り当てのセマンティクスにも関係しています:
storage
とmemory
(もしくはcalldata
から)の間での割り当ては常に独立したコピーを作成します。memory
からmemory
への割り当てでは参照のみ作られます。つまり、memory変数への変化は同じデータを参照している他のmemory変数からも可視であるということです。storage
からローカル変数へは参照のみ割り当てられます。- 他の
storage
への割り当ては常に全てコピーとなります。この例としては、状態変数への割り当てや、たとえローカル変数がただの参照だったとしてもstorageの構造型の要素への割り当てはコピーになります。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint[] x; // the data location of x is storage
// the data location of memoryArray is memory
function f(uint[] memory memoryArray) public {
x = memoryArray; // works, copies the whole array to storage
uint[] storage y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.length = 2; // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated:
// y = memoryArray;
// This does not work either, since it would "reset" the pointer, but there
// is no sensible location it could point to.
// delete y;
g(x); // calls g, handing over a reference to x
h(x); // calls h and creates an independent, temporary copy in memory
}
function g(uint[] storage) internal pure {}
function h(uint[] memory) public pure {}
}
Arrays¶
配列はコンパイル時での固定サイズか動的サイズです。
固定サイズ k
で要素の型が T
の配列は T[k]
の様に記述されます。そして動的サイズの配列は T[]
の様に書けます。
例えば、5個の uint
動的配列の配列は uint[][5]
の様に書けます。この記法は他の記法でも使われています。Solidityでは、たとえ X
自体が配列だとしても X[3]
は常に3つの要素を含んだ X
型となります。これは例えばC言語の様な他の言語とは異なっています。
インデックスはゼロベースで、アクセスする際には宣言とは逆方向でアクセスします。
例えば、もし uint[][5] x memory
を持っていたら、3つ目の動的配列に入っている2つ目の uint
には x[2][1]
でアクセスできます。また、3つ目の動的配列自体には x[2]
でアクセスします。繰り返しですが、T[5] a
という配列(T
自体は配列でも良い)を持っていたら、a[2]
というのは常に T
という型になります。
配列の要素はマッピングや構造体含めてどの型でも構いません。全般的な制限として、マッピングは storage
にのみ保存され、publicなファンクションは ABI types であるパラメータが必要になります。
配列に public
をつけることでSolidityが getter を生成します。
インデックスがgetterのパラメータになります。
そのサイズ以上の配列要素にアクセスしようとするとフェイルアサーションが発生します。新しい要素を配列の最後に追加するために .push()
が、新しいサイズを指定するのに .length
member が使用できます(下記補足を参照ください)。
bytes
and strings
as Arrays¶
bytes
と string
型の変数は特別な配列となります。bytes
は byte[]
と似ていますが、calldataとmemoryに保存されています。string
は bytes
と等価ですが、lengthとインデックスによるアクセスができません。
SolidityはStringを操作するファンクションがありませんが、同じ機能を使うための暗黙の変換が使えます。例えば、2つのstringを比較するためには keccak256(abi.encode(s1)) == keccak256(abi.encode(s2))
、エンコードされた2つのstringを連結させるには abi.encodePacked(s1, s2);
を使うことができます。
byte[]
は要素間を埋めるのに31バイト追加するので、byte[]
よりその分安い bytes
を使用する方が良いでしょう。全般的なルールとして、bytes
は任意の長さの生のバイトデータを、string
を任意の長さのstring (UTF-8)データを使用するために使ってください。もしバイト長に制限を咥えられるのであれば、非常に低コストに抑えられるため、常に``bytes1`` から bytes32
までのいずれかを使用してください。
注釈
もしバイト表現のある文字列 s
にアクセスしたい場合は、bytes(s).length
/ bytes(s)[7] = 'x';
を使ってください。この際、低レベルのUTF-8表現にアクセスしているのであって、個々の文字にアクセスしている訳ではないということを覚えておいてください。
Allocating Memory Arrays¶
メモリー内のランタイム依存の長さを持つ配列を作成するには new
というキーワードを使う必要があります。storageの配列とは逆で、(.length
を使ったりして)memoryの配列の長さを変えることはできません。事前に長さを計算しておくか、新しいmemoryの配列を作成して全ての要素をコピーする必要があります。
pragma solidity >=0.4.16 <0.6.0;
contract C {
function f(uint len) public pure {
uint[] memory a = new uint[](7);
bytes memory b = new bytes(len);
assert(a.length == 7);
assert(b.length == len);
a[6] = 8;
}
}
Array Literals¶
配列リテラルは角括弧 ([...]
)で囲まれ、カンマで区切られた1つ以上のリストを持っています(例えば [1, a, f(3)]
)。全ての要素が暗黙的に変換できる共通の型が存在しなければなりません。これはその配列の基本型になります。
配列リテラルは常に静的サイズのmemoryの配列となります。
下記の例で、[1, 2, 3]
の型は uint8[3] memory
です。各値の型が uint8
ですので、もし uint[3] memory
の結果が欲しい場合には、最初の要素を uint
型に変換する必要があります。
pragma solidity >=0.4.16 <0.6.0;
contract C {
function f() public pure {
g([uint(1), 2, 3]);
}
function g(uint[3] memory) public pure {
// ...
}
}
固定サイズのmemoryの配列は可変サイズのmemoryの配列に割り当てることはできません。例えば、次の例の様なことはできません:
pragma solidity >=0.4.0 <0.6.0;
// This will not compile.
contract C {
function f() public {
// The next line creates a type error because uint[3] memory
// cannot be converted to uint[] memory.
uint[] memory x = [uint(1), 3, 4];
}
}
この制約は将来的には削除する予定ですが、ABI内で配列の受け渡し方で複雑になってしまいます。
Array Members¶
- length:
- 配列はその要素の長さを含む
length
というメンバを持っています。 memory配列の長さは作成時に固定されます(動的配列はランタイムのパラメータによります)。 動的配列(storageでのみ使用可)に関して、このメンバは配列のサイズを変えるのに使用できます。 そのサイズ以上の配列要素にアクセスしようとすると、自動でサイズを変更するのではなく、フェイルアサーションが発生します。 長さを大きくすると、ゼロ初期化された要素が配列に加わります。長さを減らすと、削除された各要素に対してdelete
を暗黙的に行います。もしstorage出ない非動的配列のリサイズを行おうとすると、Value must be an lvalue
というエラーが発生します。 - push:
- 動的storage配列と
bytes``( ``string
ではなく)はpush
というファンクションを持ち、配列の最後に要素を追加することができます。その要素はゼロ初期化されます。ファンクションは新しい長さを返します。 - pop:
- 動的storage配列と
bytes``( ``string
ではなく)はpop
というファンクションを持ち、配列の最後から一つの要素を削除することができます。これも暗黙的に削除する要素に対してdelete
を呼び出しています。
警告
もし .length--
を空の配列に対して使うのと、アンダーフローし、長さが 2**256-1
となってしまいます。
注釈
storageの配列の長さを増やすことで一定のガスがかかります。これはstorageがゼロ初期化されているとみなされているためです。一方で、長さを減らすのは少なくとも比例関数的にコストがかかります(しかしほとんどの場合比例関数より高くなります)。これは、delete
を呼び出す様に明示的に削除した要素をクリアする工程を含んでいるためです。
注釈
まだ配列の配列をexternalのファンクションで使用することはできません(ただし、publicのファンクションではサポートされています)。
注釈
Byzantiumの前のEVMのバージョンではファンクションコールからの返り値として動的配列にはアクセスできませんでした。もし動的配列を返すファンクションんを呼び出すときは、ByzantiumモードがセットされているEVMを使っていることを確認して下さい。
pragma solidity >=0.4.16 <0.6.0;
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
// Note that the following is not a pair of dynamic arrays but a
// dynamic array of pairs (i.e. of fixed size arrays of length two).
// Because of that, T[] is always a dynamic array of T, even if T
// itself is an array.
// Data location for all state variables is storage.
bool[2][] m_pairsOfFlags;
// newPairs is stored in memory - the only possibility
// for public contract function arguments
function setAllFlagPairs(bool[2][] memory newPairs) public {
// assignment to a storage array performs a copy of ``newPairs`` and
// replaces the complete array ``m_pairsOfFlags``.
m_pairsOfFlags = newPairs;
}
struct StructType {
uint[] contents;
uint moreInfo;
}
StructType s;
function f(uint[] memory c) public {
// stores a reference to ``s`` in ``g``
StructType storage g = s;
// also changes ``s.moreInfo``.
g.moreInfo = 2;
// assigns a copy because ``g.contents``
// is not a local variable, but a member of
// a local variable.
g.contents = c;
}
function setFlagPair(uint index, bool flagA, bool flagB) public {
// access to a non-existing index will throw an exception
m_pairsOfFlags[index][0] = flagA;
m_pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) public {
// if the new size is smaller, removed array elements will be cleared
m_pairsOfFlags.length = newSize;
}
function clear() public {
// these clear the arrays completely
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// identical effect here
m_pairsOfFlags.length = 0;
}
bytes m_byteData;
function byteArrays(bytes memory data) public {
// byte arrays ("bytes") are different as they are stored without padding,
// but can be treated identical to "uint8[]"
m_byteData = data;
m_byteData.length += 7;
m_byteData[3] = 0x08;
delete m_byteData[2];
}
function addFlag(bool[2] memory flag) public returns (uint) {
return m_pairsOfFlags.push(flag);
}
function createMemoryArray(uint size) public pure returns (bytes memory) {
// Dynamic memory arrays are created using `new`:
uint[2][] memory arrayOfPairs = new uint[2][](size);
// Inline arrays are always statically-sized and if you only
// use literals, you have to provide at least one type.
arrayOfPairs[0] = [uint(1), 2];
// Create a dynamic byte array:
bytes memory b = new bytes(200);
for (uint i = 0; i < b.length; i++)
b[i] = byte(uint8(i));
return b;
}
}
Structs¶
Solidityでは次の例の様に構造体として新しい型を定義する方法があります:
pragma solidity >=0.4.11 <0.6.0;
contract CrowdFunding {
// Defines a new type with two fields.
struct Funder {
address addr;
uint amount;
}
struct Campaign {
address payable beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping (uint => Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
campaignID = numCampaigns++; // campaignID is return variable
// Creates new struct in memory and copies it to storage.
// We leave out the mapping type, because it is not valid in memory.
// If structs are copied (even from storage to storage), mapping types
// are always omitted, because they cannot be enumerated.
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}
function contribute(uint campaignID) public payable {
Campaign storage c = campaigns[campaignID];
// Creates a new temporary memory struct, initialised with the given values
// and copies it over to storage.
// Note that you can also use Funder(msg.sender, msg.value) to initialise.
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function checkGoalReached(uint campaignID) public returns (bool reached) {
Campaign storage c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
このコントラクトはクラウドファンディングの全機能を備えている訳ではありませんが、構造体を理解するために必要な基本的な概念を含んでいます。構造体型はマッピングや配列の中でも使えますし、構造体の中にマッピングや配列を含むこともできます。
構造体自身はマッピングの要素の値型になったり、自分自身の型の動的配列を含むことはできますが、構造体の中に自分自身の構造体型を含めることはできません。構造体のサイズが有限である様にするためにこの制限が必要となっています。
これらの全てのファンクションの中で、構造体型がどの様にデータの保存場所である storage
が付いているローカル変数に割り当てられているか注意してください。構造体をコピーしているのではなく、参照先を保存しているだけなので、ローカル変数への割り当ては実際は状態のみを記述しています。
もちろん、campaigns[campaignID].amount = 0
の様にローカル変数への割り当てをせずに直接構造体へアクセスすることもできます。
Mapping Types¶
マッピング型は mapping(_KeyType => _ValueType)
という構文で宣言します。_KeyType
はどの基本型でも入ります。つまりどのビルトインの型に加え、bytes
と string
が使えます。ユーザー定義、もしくはコントラクト型、enum、マッピング、構造体、bytes
と string
を除いた配列は使用できません。_ValueType
はマッピングを含めて、どの型でもとることができます。
マッピングはどんなキーも存在し、そのバイト表現は全てゼロ(型の デフォルト値)で実質的に初期化されている hash tables の様に考えることができます。ただ似ているのはそれだけで、キーデータはマッピングには保存されず、その keccak256
ハッシュだけが値を検索するのに使用されます。
そのため、マッピングは長さを持たず、キーの概念やセットする値などはありません。
マッピングは storage
にのみデータを置けるため、ファンクション内のstorage参照型、もしくはライブラリファンクションのパラメータとしての状態変数として使用されます。パラメータやパブリックにアクセスできるコントラクトファンクションの返り値としては使用できません。
マッピングタイプに public
修飾子をつけることができます。Solidityはそれにより getter を生成します。_KeyType
はゲッターのパラメータになります。もし _ValueType
が値型か構造体の場合、ゲッターは _ValueType
を返します。もし _ValueType
が配列かマッピングであれば、ゲッターは各 _KeyType
に対して一つのパラメータを再帰的に持ちます。以下マッピングの例です:
pragma solidity >=0.4.0 <0.6.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(address(this));
}
}
注釈
マッピングはiterableではありませんが、マッピング上のデータ構造を実装することは可能です。具体例は iterable mapping を参照ください。
Operators Involving LValues¶
もし a
がLValue(変数もしくは何か割り当てが行えるもの)であれば、以下の演算子が短縮記法として使えます:
a += e
は a = a + e
と等価です。 演算子 -=
, *=
, /=
, %=
, |=
, &=
, ^=
も同様に定義されます。a++
と a--
は a += 1
/ a -= 1
と等価ですがこの式自体は計算前の値 a
です。一方、--a
と ++a
は a
に対して同じ計算を行いますが、この式自体は計算後の値を持ちます。
delete¶
delete a
は a
に初期値を割り当てます。例えば、整数型であれば a = 0
に相当します。しかし、長さゼロの動的配列や全ての要素が初期値で同じ長さの静的配列も使用可能です。delete a[x]
はインデックス x
の配列の要素を削除し、他のは残し配列の長さは変えません。つまり配列に空白を残します。もし要素を削除する予定であれば、おそらくマッピングの方がよいでしょう。
構造体に対しては全ての要素をリセットした上で初期値を割り当てます。言い換えると、delete a
の後は a
があたかも値の代入されていないただ宣言されただけの a
と同じ扱いができます。ただし、以下の様に注意が必要です:
delete
はマッピングには効きません(マッピングのキーはおそらく任意であり、一般に未知のためです)。そのため、もし構造体を削除したら、マッピングではない全ての要素はリセットされ、マッピング以外の要素は再帰的に処理されます。しかし、個々のキーと、マッピングしたものは削除することができます: a
がマッピングであれば、delete a[x]
で x
に保存されている値は削除されます。
覚えておきたいのは delete a
は a
への値の代入の様に振舞うことです。a
に新しいオブジェクトを保存します。この特徴は a
が参照型の場合分かりやすいです: a
そのものだけリセットし、元々参照していた値はリセットしません。
pragma solidity >=0.4.0 <0.6.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() public {
uint x = data;
delete x; // sets x to 0, does not affect data
delete data; // sets data to 0, does not affect x
uint[] storage y = dataArray;
delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also
// y is affected which is an alias to the storage object
// On the other hand: "delete y" is not valid, as assignments to local variables
// referencing storage objects can only be made from existing storage objects.
assert(y.length == 0);
}
}
Conversions between Elementary Types¶
Implicit Conversions¶
もし演算子が違う型に使われた場合、コンパイラは暗黙のうちに演算対象の型を変換します(代入、割り当て時も同様です)。一般に、セマンティック的に同じ扱いができ、何の情報も失われないのであれば、暗黙の値型の変換は可能です: uint8
は uint16
に、int128
は int256
に変換可能ですが、int8
は uint256
に変換不可です(uint256
は例えば -1
を持てないからです)。
詳細は型のセクションを参照してください。
Explicit Conversions¶
もしコンパイラが暗黙の型変換を行わず、しかしあなたが何をしようとしているのか自分で理解しているのであれば、明示的な型変換は可能な時もあります。しかし、これは予想しない挙動や、コンパイラのセキュリティ的な機能をバイパスする可能性もあるので、結果が予想通りになるかテストを行ってください。以下の例は、負の int8
を uint
に変換しています。
int8 y = -3;
uint x = uint(y);
このコードスニペットの最後では x
は 0xfffff..fd
(64文字の16進数)という値を持ち、これは256ビットの2の補数表現です。
もしある整数が明示的に小さい型に変換された場合、上側のビットがカットされます:
uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now
もしある整数が大きい型に明示的に変換され場合は、左側に(上側のビットに)パディングされます。変換後の結果と元の整数を比較した場合、それらは等しいものとして扱われます:
uint16 a = 0x1234;
uint32 b = uint32(a); // b will be 0x00001234 now
assert(a == b);
固定長のバイト型の変換は異なった挙動をします。個々のバイトのシーケンスとして扱われ、小さいサイズへの型変換時にはそのシーケンスがカットされます:
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b will be 0x12
もし固定サイズのバイト型が明示的に大きい型に変換された場合、右側がパディングされます。(もしインデックスが範囲内にあるのであれば)変換前後で同じインデックスで同じ値を返します:
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b will be 0x12340000
assert(a[0] == b[0]);
assert(a[1] == b[1]);
切り取りやパディングの際に、整数と固定サイズのバイト配列は異なった挙動を示すため、整数と固定サイズのバイト配列の明示的な変換は二つが同じサイズである場合のみ許可されます。もし異なるサイズの整数と固定サイズのバイト配列を変換したい時は、理想的な切り取りやパディングルールを明示的にする中継的な変換を使用する必要があります:
bytes2 a = 0x1234;
uint32 b = uint16(a); // b will be 0x00001234
uint32 c = uint32(bytes4(a)); // c will be 0x12340000
uint8 d = uint8(uint16(a)); // d will be 0x34
uint8 e = uint8(bytes1(a)); // e will be 0x12
Conversions between Literals and Elementary Types¶
Integer Types¶
切り捨てなしに表せるほど十分大きければ10進数と16進数のリテラルは暗黙的にどの整数型にも変換可能です:
uint8 a = 12; // fine
uint32 b = 1234; // fine
uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456
Fixed-Size Byte Arrays¶
10進数の数字リテラルは暗黙的に固定サイズのバイト配列に変換できません。16進数の数字リテラルはバイト型のサイズと桁数がぴったり合っている場合のみ変換可能です。10進数、16進数両者の唯一の例外として、値が0であればどの固定サイズのバイト型に変換できます:
bytes2 a = 54321; // not allowed
bytes2 b = 0x12; // not allowed
bytes2 c = 0x123; // not allowed
bytes2 d = 0x1234; // fine
bytes2 e = 0x0012; // fine
bytes4 f = 0; // fine
bytes4 g = 0x0; // fine
文字数とバイト型のサイズが合っていれば、文字列リテラルと16進数文字列リテラルは暗黙的に固定サイズバイト配列に変換できます:
bytes2 a = hex"1234"; // fine
bytes2 b = "xy"; // fine
bytes2 c = hex"12"; // not allowed
bytes2 d = hex"123"; // not allowed
bytes2 e = "x"; // not allowed
bytes2 f = "xyz"; // not allowed
Addresses¶
Address Literals で説明したように、チェックサムテストが通る正しいサイズの16進数リテラルは アドレス
型です。他のリテラルは暗黙的に アドレス
型への変換はできません。
bytes20
もしくは他の整数型から address
への明示的な変換を行うと address payable
になります。
Units and Globally Available Variables¶
Ether Units¶
リテラルの数値はEhterの貨幣の単位として wei
、finney
、szabo
もしくは ether
を接尾辞として取れます。接尾辞がないEtherはWeiであるとみなされます。
assert(1 wei == 1);
assert(1 szabo == 1e12);
assert(1 finney == 1e15);
assert(1 ether == 1e18);
貨幣の単位を表す接尾辞は10の累乗を乗ずるだけです。
Time Units¶
リテラルの数値の後の seconds
、minutes
、hours
、days
、weeks
の様な接尾辞は時間の単位を指定するのに使用されます。秒を基準として単位は単純に下記の様に決められます。
1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks == 7 days
これらの単位を使ってカレンダーの計算をするのであれば気を付けてください。うるう秒 のせいで、1年は毎年365日というわけではなく、1日も必ずしも24時間ではありません。うるう秒は予想できないので、カレンダーライブラリは外部のオラクルによってアップデートされる必要があります。
注釈
上記の理由により years
という接尾辞はバージョン0.5.0で削除されました。
これらの接尾辞は変数には使えません。例えばもしファンクションのパラメータをdaysにしたい場合には下記の方法で変換できます:
function f(uint start, uint daysAfter) public {
if (now >= start + daysAfter * 1 days) {
// ...
}
}
Special Variables and Functions¶
グローバルな名前空間にあり、主にブロックチェーンに関する情報や一般的な用途で使うユーティリティのファンクションを提供する特別な変数とファンクションがあります。
Block and Transaction Properties¶
blockhash(uint blockNumber) returns (bytes32)
: 与えられたブロックのハッシュ - 現在のブロックを除いた直近256個のブロックのみで有効block.coinbase
(address payable
): 現在のブロックのマイナーのアドレスblock.difficulty
(uint
): 現在のブロックのdifficultyblock.gaslimit
(uint
): 現在のブロックのガスリミットblock.number
(uint
): 現在のブロックナンバーblock.timestamp
(uint
): 現在のunixのタイムスタンプ(秒)gasleft() returns (uint256)
: 残ガスmsg.data
(bytes calldata
): コールデータmsg.sender
(address payable
): メッセージの送信者msg.sig
(bytes4
): コールデータの始め4byte(例:ファンクションの識別子)msg.value
(uint
): メッセージと一緒に送られたweiの量now
(uint
): 現在のブロックのタイムスタンプ (block.timestamp
のエイリアス)tx.gasprice
(uint
): トランザクションのガスプライスtx.origin
(address payable
): トランザクションの送信者 (フルコールチェーン)
注釈
msg.sender
と msg.value
を含んだ msg
の値は external のファンクションのコールごとに変えることができます。これはライブラリのファンクションでも同様です。
注釈
自分のコードで何をしているか把握していない限り、block.timestamp
、now
と blockhash
を乱数のソースとして信用しないでください。
タイムスタンプとブロックハッシュはある程度マイナーによって影響されます。悪意を持ったマイナーは例えばあるハッシュでカジノの支払いファンクションを呼び出し、もしお金を受け取れなかったら、また別のハッシュでそのファンクションを呼び出すことができます。
現在のブロックのタイムスタンプは最後のブロックより確実に大きい必要がありますが、保証されているのはタイムスタンプは2つの連続する標準ブロックの間であるということだけです。
注釈
ブロックハッシュはスケーラビリティの理由から全てのブロックに対して利用可能という訳ではありません。最新256ブロックのハッシュにのみアクセス可能で、それ以前の値はゼロになります。
注釈
blockhash
ファンクションは以前は block.blockhash
でしたが、バージョン0.4.22で非推奨になり、バージョン0.5.0で削除されました。
注釈
gasleft
ファンクションは以前は msg.gas
でしたが、バージョン0.4.21で非推奨になり、バージョン0.5.0で削除されました。
ABI Encoding and Decoding Functions¶
abi.decode(bytes memory encodedData, (...)) returns (...)
: 与えられたデータをABIデコードする。第二引数として型を括弧付きで与えます。例:(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))
abi.encode(...) returns (bytes memory)
: 引数をABIエンコードします。abi.encodePacked(...) returns (bytes memory)
: 与えられた引数で packed encoding を行います。abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)
: 与えられた引数を二番目からABIエンコードし、その前に与えられた4バイトのセレクタを追加します。abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)
:abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)`
と同じです。
注釈
これらのエンコーディングのファンクションは実際に外部のファンクションを呼ぶことなく外部のファンクション用のデータを作るために使われます。さらに、keccak256(abi.encodePacked(a, b))
は構造化されたデータのハッシュを計算する方法です。(異なるファンクションのパラメータの型を使ってもハッシュ衝突を起こす可能性があることに気をつけてください。)
エンコーディングの詳細は公式ドキュメントの ABI と tightly packed encoding を参照ください。
Error Handling¶
エラーハンドリングに対する細かな詳細と、どのファンクションをいつ使うに関しては assert require にあるそれらに特化したセクションを参照ください。
assert(bool condition)
:- 条件を満たしていないと、invalid opcodeを発生させ、その結果state change reversionが起きます - 内部エラーに使用されます。
require(bool condition)
:- 条件を満たしていないと、revertします - 入力か外部要素に対してのエラーに使用されます。
require(bool condition, string memory message)
:- 条件を満たしていないと、revertします - 入力か外部要素に対してのエラーに使用されます。加えてエラーメッセージも出力されます。
revert()
:- 実行を中断し、stateの変化を元に戻します。
revert(string memory reason)
:- 説明付きで実行を中断し、stateの変化を元に戻します。
Mathematical and Cryptographic Functions¶
addmod(uint x, uint y, uint k) returns (uint)
:- 任意の精度で
(x + y) % k
の加算を行い、2**256
でラップアラウンドしません。バージョン0.5.0からはk != 0
のアサーションを行います。 mulmod(uint x, uint y, uint k) returns (uint)
:- 任意の精度で
(x * y) % k
の加算を行い、2**256
でラップアラウンドしません。バージョン0.5.0からはk != 0
のアサーションを行います。 keccak256(bytes memory) returns (bytes32)
:- 入力に対してKeccak-256のハッシュを計算します。
sha256(bytes memory) returns (bytes32)
:- 入力に対してSHA-256のハッシュを計算します。
ripemd160(bytes memory) returns (bytes20)
:- 入力に対してRIPEMD-160のハッシュを計算します。
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
:- 楕円曲線の署名から公開鍵に関連したアドレスを復元する、もしくはエラーでゼロを返します。(使用例)
注釈
ecrecover
は address
を返し、address
payable
は返しません。復元されたアドレスで送金を行いたい場合には、変換するために address payable を参照ください。
プライベートなブロックチェーン 上では sha256
、ripemd160
もしくは ecrecover
でガス不足になるかもしれません。理由としては、これらはいわゆるプレコンパイルされたコントラクトとして実行され、そのコントラクトが本当に存在するのは、最初のメッセージを受け取った後だからです(そのコントラクトはハードコードですが)。存在しないコントラクトへのメッセージは高価なため、ガス不足になります。この問題の回避策としては例えば実際のコントラクトでそのファンクションを使う前に最初に1Weiをそのコントラクトに送ることです。メインネットやテストネットではこの問題は起こりません。
注釈
sha3
と呼ばれる keccak256
のエイリアスがありましたが、バージョン0.5.0で削除されました。
Members of Address Types¶
<address>.balance
(uint256
):- Address のバランス(Wei)
<address payable>.transfer(uint256 amount)
:- 与えられたWeiを Address に送ります。失敗するとリバートし、固定で2300ガスを送ります。 (変更不可です。)
<address payable>.send(uint256 amount) returns (bool)
:- 与えられたWeiを Address に送ります。失敗すると
false
を返し、固定で2300ガスを送ります。 (変更不可です。) <address>.call(bytes memory) returns (bool, bytes memory)
:- 低レベルの
CALL
を、与えられたペイロードと共に発行し、成否とデータを返し、使用可能なガスを全て送ります。(変更可能です。) <address>.delegatecall(bytes memory) returns (bool, bytes memory)
:- 低レベルの
DELEGATECALL
を、与えられたペイロードと共に発行し、成否とデータを返し、使用可能なガスを全て送ります。(変更可能です。) <address>.staticcall(bytes memory) returns (bool, bytes memory)
:- 低レベルの
STATICCALL
を、与えられたペイロードと共に発行し、成否とデータを返し、使用可能なガスを全て送ります。(変更可能です。)
詳細は Address を参照ください。
警告
タイプチェックやファンクションの存在チェック、引数のパッキングをバイパスするため、他のコントラクトのファンクションを実行する際には極力 .call()
の使用を避けてください。
警告
send
を使うことにはいくつかの危険があります: コールスタックの深さが1024で送金は失敗します(これは呼び出し元によっていつも強制されます)。そして、受領者のガスが不足した際にも送金は失敗します。そのため、安全にEtherを送るために、常に send
の返り値を確認する、もしくは transfer
を使用してください。もっと良い手段は受領者がお金を引き出す時のパターンを使用することです。
注釈
バージョン0.5.0以前では、例えば this.balance
の様にコントラクトインスタンスからアドレス型のメンバにアクセス可能でした。現在ではこれは禁止されており、明示的にアドレス型に変換する必要があります:address(this).balance
。
注釈
もし状態変数が低レベルdelegatecallを通じてアクセスされた場合、呼び出されたコントラクトが呼び出したコントラクトのストレージ変数に名前で正しくアクセスできる様に2つのコントラクトのストレージの配置は揃ってなければいけません。 これはもちろんストレージのポインタがファンクションの引数で渡される場合ではなく、高レベルのライブラリの場合です。
注釈
バージョン0.5.0以前では, .call
、.delegatecall
、.staticcall
は成否だけ返し、データは返しません。
注釈
バージョン0.5.0以前では, callcode
と呼ばれる delegatecall
に似ていますが、微妙に異なるメンバがあります。
Type Information¶
type(X)
という表現は X
型についての情報を引き出すのに使用可能です。現在、この機能について限定的なサポートしかありませんが、将来拡張されるかもしれません。以下のプロパティはコントラクト型 C
で使用可能です。
type(C).creationCode
:- コントラクトのバイトコードの生成を含んでいるメモリーバイト配列
カスタムクリエーションルーティンを作るためにインラインアッセンブリで使用できます(特に
create2
opcodeを使って)。 このプロパティはコントラクト自体、もしくは継承元のコントラクトからは呼び出すことができません。そのため、呼び出し元のバイトコードにこのバイトコードが含まれ、その結果循環参照が不可能になります。 type(C).runtimeCode
:- コントラクトのランタイムバイトコードを含んでいるメモリーバイト配列
これは通常、
C
のコンストラクタによってデプロイされるコードです。もし``C``がインラインアセンブリを使うコンストラクタを持っていたら、実際のデプロイされるバイトコードとは異なるかもしれません。通常の呼び出しから保護するために、デプロイ時にライブラリがランタイムバイトコードを修正するということを覚えておいてください。 同じ制限が.creationCode
と同様にこのプロパティに適用されます。
Expressions and Control Structures¶
Control Structures¶
Curly-braces languageで使われる様な制御構造のほとんどはSolidityでも使用可能です:
CやJavaScriptで知られる通常のセマンティクスに加えて、if
、else
、while
、do
、for
、break
、continue
、return
があります。
条件文では括弧は省略できませんが、単文の周りの波括弧は省略可能です。
CやJavaScriptの様に非ブーリアンをブーリアンに変換することはできません。つまり if (1) { ... }
はSolidityでは有効ではありません。
Function Calls¶
Internal Function Calls¶
現在のコントラクトのファンクションは直接("内部的に")呼び出すことができます。また、下記の無意味な例の様に再帰的に呼び出すこともできます。:
pragma solidity >=0.4.16 <0.6.0;
contract C {
function g(uint a) public pure returns (uint ret) { return a + f(); }
function f() internal pure returns (uint ret) { return g(7) + f(); }
}
これらのファンクションの呼び出しはEVM内部で単純なジャンプとして変換されます。これには現在のメモリがクリアされないという影響があります。例えば、メモリの参照を内部で呼ばれたファンクションに渡すことは非常に効率的です。同じコントラクトのファンクションのみが内部的に呼び出すことが可能です。
全てのinternalファンクションの呼び出しは少なくとも1つのスタックのスロットを使い、最大で1024個しかそのスロットは使えないので、余計な再帰処理は避けたほうが良いでしょう。
External Function Calls¶
this.g(8);
と c.g(2);
という表現(c
はコントラクトのインスタンス)も有効なファンクションの呼び出しですが、今回はファンクションは直接ジャンプするのではなく、メッセージコールを通じて "外部的に" 呼び出されます。
実際のコントラクトは未だ作られていないため、this
でのファンクションの呼び出しはコンストラクタでは使用することができないということを覚えておいてください。
他のコントラクトのファンクションは外部的に呼び出す必要があります。外部コールに関して、全てのファンクションの引数はmemoryにコピーされなければいけません。
注釈
あるコントラクトから別のコントラクトへのファンクションコールはトランザクションを生成しません。トランザクション全体の一部としてのメッセージコールとなります。
他のコントラクトのファンクションを呼び出すときに、送るWeiの量やガスを特別なオプション .value()
と .gas()
を使ってそれぞれ決めることができます。送ったWeiは全てコントラクトのトータルバランスに追加されます。
pragma solidity >=0.4.0 <0.6.0;
contract InfoFeed {
function info() public payable returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed;
function setFeed(InfoFeed addr) public { feed = addr; }
function callFeed() public { feed.info.value(10).gas(800)(); }
}
info
ファンクションではmodifierの payable
を使う必要があります。使わない場合、.value()
オプションが使えません。
警告
feed.info.value(10).gas(800)
は value
とファンクションコールと一緒に送信される gas
量をローカルにセットするだけです。そして、最後の括弧が実際にファンクションをコールしています。そのため、このケースではファンクションは呼ばれていません。
呼ばれたコントラクトが存在しない場合(アカウントがコードを含んでいない場合)、もしくは呼ばれたコントラクト自体がエラーを投げた場合、ガス不足の場合にはファンクションが呼ばれると例外が発生します。
警告
他のコントラクトのやり取りは、潜在的な危険をはらんでいます。特に、事前にそのコントラクトのソースコードを知らなかった場合には危険です。現在のコントラクトは呼ばれたコントラクトにコントロールを渡すと、潜在的には何でもできてしまうかもしれません。たとえ呼ばれたコントラクトが既知の親コントラクトを継承していたとしても、継承したコントラクトは正しいインターフェースを持てば良いだけです。しかし、そのコントラクトの実行は完全に任意ですので、危険をもたらします。また、そのコントラクトがあなたのシステムの他のコントラクトを呼び出したり、初めの呼び出しが帰って来る前に呼び出し元のコントラクトを呼び返す様な事態に備えてください。つまり呼ばれたコントラクトはファンクションを通じて呼び出し元のコントラクトの状態変数を変えることができます。例えば、コントラクト内の状態変数の変更の後に外部のファンクションを呼び出すような形でファンクションを作ってください。そうすれば、あなたのコントラクトはリエントラントの悪用に対して攻撃されにくくなるでしょう。
Named Calls and Anonymous Function Parameters¶
ファンクションよ呼ぶ際の引数は { }
で囲えば下記の例のように任意の順番で、名前を与えることができます。引数の名前はファンクションの宣言の際のパラメータと一致する必要がありますが、順番は任意です。
pragma solidity >=0.4.0 <0.6.0;
contract C {
mapping(uint => uint) data;
function f() public {
set({value: 2, key: 3});
}
function set(uint key, uint value) public {
data[key] = value;
}
}
Omitted Function Parameter Names¶
使われなかったパラメータの名前(特に返り値)は省略することができます。 そのパラメータはスタック上に残りますが、アクセスできません。
pragma solidity >=0.4.16 <0.6.0;
contract C {
// omitted name for parameter
function func(uint k, uint) public pure returns(uint) {
return k;
}
}
Creating Contracts via new
¶
コントラクトは new
というキーワードを使って他のコントラクトを作ることができます。コントラクトを作るコントラクトがコンパイルされる時に、作られるコントラクトのフルコードは既知である必要があります。そうすれば、再帰的なcreation-dependenciesは起きません。
pragma solidity ^0.5.0;
contract D {
uint public x;
constructor(uint a) public payable {
x = a;
}
}
contract C {
D d = new D(4); // will be executed as part of C's constructor
function createD(uint arg) public {
D newD = new D(arg);
newD.x();
}
function createAndEndowD(uint arg, uint amount) public payable {
// Send ether along with the creation
D newD = (new D).value(amount)(arg);
newD.x();
}
}
上記の例の様に、.value()
オプションを使って、D
のインスタンスを作る時に、Etherを送ることができます。しかし、ガス量を制限することはできません。もし生成が失敗(スタックの制限、バランスの不足等で)したら、例外が投げられます。
Order of Evaluation of Expressions¶
式の評価の順番は決まっていません(正確には、式ツリーの中の1つのノードの子が評価される順番は決まっていませんが、そのノードが評価される前にはもちろん評価されています)。唯一保証されているのは、宣言は順番に実行され、boolean式に対する短絡評価もなされます。詳細は Order of Precedence of Operators を参照ください。
Assignment¶
Destructuring Assignments and Returning Multiple Values¶
Solidityは内部ではタプル型を許可しています。例えば、コンパイル時に定数を持つ潜在的に異なるタイプのオブジェクトのリストです。 このタプルは同時に複数の値を返す時に使うことができます。これらは新しく宣言された変数や既に存在している変数(もしくは一般的に左辺値)に割り当てることができます。
タプルはSolidityでは適切な型ではありません。式のシンタックスのグルーピングを作るためだけに使用することができます。
pragma solidity >0.4.23 <0.6.0;
contract C {
uint[] data;
function f() public pure returns (uint, bool, uint) {
return (7, true, 2);
}
function g() public {
// Variables declared with type and assigned from the returned tuple,
// not all elements have to be specified (but the number must match).
(uint x, , uint y) = f();
// Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x);
// Components can be left out (also for variable declarations).
(data.length, , ) = f(); // Sets the length to 7
}
}
変数の宣言と、宣言なしの値の割り当てを混ぜることはできません。例えば、次の式は無効です: (x, uint y) = (1, 2);
注釈
バージョン0.5.0以前では、片側が小さいサイズで、左辺か右辺(空の方)を補完する様なタプルを割り当てることができました。 現在これは禁止です。つまり両辺とも同じ数の要素を持っている必要があります。
警告
参照型を含む複数の変数を同時に割り当てる際には気をつけてください。予期しないコピーを行う可能性があります。
Complications for Arrays and Structs¶
配列や構造体の様な非値型に対するアサインのセマンティクスは少し複雑です。
状態変数に対する割り当ては常に独立したコピーを生成します。一方で、ローカル変数への割り当ては基本型(例えば32バイトに収まる静的タイプ)の時のみ独立したコピーを生成します。もし構造体か配列(bytes
や を含んでいる)が状態変数からローカル変数に割り当てられた場合、ローカル変数はオリジナルの状態変数への参照を持ちます。次にローカル変数へ割り当てても、ステートは変わらず、参照先のみ変更します。ローカル変数のメンバ(もしくは要素)への割り当てはステートを 変更します。
下記の例で g(x)
の呼び出しは x
に何の影響も与えません。それはmemoryに独立したstorageの値を生成するからです。しかし、h(x)
は x
を変更します。それはコピーではなく参照が渡されているからです。
pragma solidity >=0.4.16 <0.6.0;
contract C {
uint[20] x;
function f() public {
g(x);
h(x);
}
function g(uint[20] memory y) internal pure {
y[2] = 3;
}
function h(uint[20] storage y) internal {
y[3] = 4;
}
}
Scoping and Declarations¶
宣言された変数はバイト表現が全てゼロのデフォルト値を与えられます。変数の "デフォルト値" はどの型であれ一般的な "zero-state" です。例えば、bool
のデフォルト値は false
です。uint
もしくは int
型のデフォルト値は 0
です。静的サイズの配列や bytes1
から bytes32
では、個々の要素がその型に応じたデフォルト値で初期化されます。最後に、動的サイズの配列や bytes
、string
に関してはデフォルト値は空の配列もしくは空の文字列です。
Solidityでのスコープは広く使われているC99のルールに従います: 変数は宣言直後からその宣言を含む最小の { }
ブロックの最後までvisibleです。このルールの例外として、forループ内の初期化内で宣言された変数はそのforループが終わるまでの間でだけvisibleです。
コードブロック外で変数や他の宣言されたもの、例えばファンクション、コントラクト、ユーザー定義型などは宣言される前からvisibleです。つまり、宣言前から状態変数を使うことができ、ファンクションを再帰的に呼び出すことができます。
その結果、2つの変数が同じ名前を持っていますが、バラバラのスコープを持つため、下記の例は警告なしでコンパイルされます。
pragma solidity ^0.5.0;
contract C {
function minimalScoping() pure public {
{
uint same;
same = 1;
}
{
uint same;
same = 3;
}
}
}
特別なC99のスコーピングルールの例として、下記の最初の x
への割り当ては、実際はインナーの変数ではなく、アウターに割り当てられています。どの様な場合でもアウターの変数がシャドーイングされた時に警告が出ます。
pragma solidity ^0.5.0;
// This will report a warning
contract C {
function f() pure public returns (uint) {
uint x = 1;
{
x = 2; // this will assign to the outer variable
uint x;
}
return x; // x has value 2
}
}
警告
バージョン0.5.0以前のSolidityではJavaScriptと同じスコーピングのルールに従っていました。そのルールとは、ファンクション内で宣言された変数のスコープは、どこで宣言されたか関係なく、そのファンクション全体というものです。下記の例は、以前はコンパイルされていましたが、バージョン0.5.0以降はエラーとなります。
pragma solidity ^0.5.0;
// This will not compile
contract C {
function f() pure public returns (uint) {
x = 2;
uint x;
return x;
}
}
Error handling: Assert, Require, Revert and Exceptions¶
Solidityはエラーを処理するのにstate-revertingの例外を使っています。現在のコール(とそのサブコール全て)内で変更されたものを全て元に戻し、さらに呼び出し元に対してエラーのフラグをたてます。便利なファンクション assert
と require
はもし条件が満たされていない場合に例外を投げるために使用されます。
assert
は内部エラーのテストと不変条件のチェックのためだけに使用するべきです。
require
は有効な条件を保証するために使用するのが良いでしょう。例えば入力、コントラクトの状態変数が合致しているか、もしくは外部コントラクトから返ってきた値のバリデーションに使うことができます。
正しく使えば、分析ツールはコントラクトのコンディションとフェイル assert
になるファンクションコールを見極めるためにコントラクトの評価を行うことができます。正しく実装されたコードはフェイルアサートが出ないはずです。もしそれでもアサートが出た場合、それは直さなくてはいけないバグです。
例外を引き起こす方法が2つあります: revert
ファンクションはエラーにフラグを立て、現在のコールをrevertするのに使用することができます。呼び出し元に対して、詳細を含むエラーメッセージを返すことができます。
注釈
以前は throw
という revert()
と同じセマンティクスのファンクションがありました。バージョン0.4.13で非推奨になり、バージョン0.5.0で削除されました。
サブコールで例外が発生した場合、その例外は自動的にどんどん出てきます(例外が再度投げられます)。このルールに対する例外は、send
と低レベルファンクションの call
、delegatecall
、staticcall
で、これらは例外がどんどん出てこない代わりに、false
を返り値として最初に返します。
警告
低レベルファンクションの call
、delegatecall
、staticcall
は、もし呼ばれたアカウントが存在しない場合、EVMの設計上、最初の返り値として true
を返します。もし必要なら、呼び出す前に存在確認をしなければいけません。
例外のキャッチはまだ可能ではありません。
下記の例では、require
が入力に対するコンディションチェックを簡単に行なっているか見ることができます。また、assert
がどの様に内部エラーのチェックに使用されうるのか確認できます。覚えて欲しいのは、require
ではオプションでエラーメッセージを与えることができます。しかし assert
ではできません。
pragma solidity ^0.5.0;
contract Sharer {
function sendHalf(address payable addr) public payable returns (uint balance) {
require(msg.value % 2 == 0, "Even value required.");
uint balanceBeforeTransfer = address(this).balance;
addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and
// cannot call back here, there should be no way for us to
// still have half of the money.
assert(address(this).balance == balanceBeforeTransfer - msg.value / 2);
return address(this).balance;
}
}
assert
-スタイルの例外は下記のシチュエーションで発生します:
- 大きすぎるもしくは負のインデックスで配列にアクセスした場合(例:
x[i]
においてi >= x.length
もしくはi < 0
)。 - 大きすぎるもしくは負のインデックスで固定長
bytesN
にアクセスした場合。 - 0で除算、剰余算を行なった場合(例:
5 / 0
もしくは23 % 0
)。 - 負の数でシフトを行なった場合。
- とても大きい、もしくは負の数を列挙型に変換した場合。
- インターバルファンクション型のゼロ初期化された変数を呼び出した場合。
- falseとなる引数で
assert
を呼び出した場合。
require
-スタイルの例外は下記のシチュエーションで発生します:
- falseとなる引数で
require
を呼び出した場合。 - メッセージコールを通じてファンクションを呼び出したが、正常に終わらなかった場合(例えば、ガス不足、マッチするファンクションがなかった場合、それ自体が例外を投げた場合)、ただし低レベルのファンクション、
call
、send
、delegatecall
、callcode
もしくはstaticcall
が使用された場合を除きます。低レベルファンクションは例外を投げませんが、false
を返すことで失敗を示します。 new
を使ってコントラクトを作ったものの、そのコントラクト作成が正常に終わらなかった場合("正常に終わらない"の定義は上記参照)。- 何もコードを含まないコントラクトを外から呼び出した場合。
- コントラクトが
payable
modifierが付いていないpublicのファンクションを通じてEtherを受け取った場合(コンストラクタ、フォールバックファンクションを含む)。 - コントラクトがpublicのgetterファンクションを通じてEtherを受け取った場合。
.transfer()
が失敗した場合。
内部では、Solidityは require
-スタイルの例外に対してrevert操作(0xfd
命令)を行います。また、require
-スタイルの例外を投げるために、無効な操作を実行します。どちらのケースにおいても、ステートに対してなされた変更の全てをEVMがrevertする様にします。revertする理由は、期待した効果が実際には起きず、命令の実行を続ける安全な方法がないためです。トランザクションの原子性(一連のやりとりが全て実行されるか、1つも実行されない状態になること)を保ちたいので、最も安全な方法は全ての変更をrevertし、トランザクション全体(もしくは少なくとも呼び出し)の結果を無効にすることです。assert
-スタイルの例外は全ての使用可能なガスをその呼び出しに使ってしまう一方で、require
-スタイルの例外はガスを使いません。これはMetropolisがリリースされた時からスタートしました。
以下の例は、どの様にrevertとrequireでエラーメッセージが使われているかを示しています:
pragma solidity ^0.5.0;
contract VendingMachine {
function buy(uint amount) public payable {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
// Alternative way to do it:
require(
amount <= msg.value / 2 ether,
"Not enough Ether provided."
);
// Perform the purchase.
}
}
与えられた文字列は、abi-encoded となります。そしてそれはまるで Error(string)
ファンクションを呼び出している様なものです。
上記の例では、revert("Not enough Ether provided.");
は下記のエラーの返り値としてセットされた16進数データを生成します:
0x08c379a0 // Function selector for Error(string)
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data
Contracts¶
Solidityに置けるコントラクトはオブジェクト指向の言語におけるクラスに似ています。コントラクトは永続的な状態変数とそれを変更するファンクションを保持しています。異なるコントラクト上(インスタンス上)のファンクションコールはEVMのファンクションコールとなり、その結果コンテクストが変わり、状態変数にアクセスできなくなります。コントラクトとそのファンクションは呼び出さないと動きません。Ethereumには"cron"の様に特定のイベントで自動的にファンクションを呼び出す機能はありません。
Creating Contracts¶
コントラクトはEthreumトランザクションを通じて"外部から"、もしくはSolidityコントラクトの内側から作成可能です。
Remix の様なIDEはUIの要素を使って、シームレスに生成プロセスを作ります。
Ethreum上にプログラムでコントラクトを作成するにはJavaScript API web3.js を使うのがベストです。 web3.jsは簡単にコントラクトを作るための web3.eth.Contract というファンクションを持っています。
コントラクトが作られた時、constructor ( constructor
キーワードで宣言されるファンクション)が一度だけ実行されます。
コンストラクタはオプションです。1つのコンストラクタだけ使えます。つまりオーバーロードはサポートされていません。
コンストラクタが実行された後、最終的なコントラクトのコードがブロックチェーン上にデプロイされます。このコードは全てのpublic、externalのファンクションを含んでおり、全てのファンクションはファンクションコールを通じてそのコードからアクセスできます。デプロイされたコードはコンストラクタもしくはコンストラクタによって呼ばれたinternalのファンクションだけ含んでいません。
内部的にはコントラクトのコードの後、コンストラクタの引数は ABI encoded されて渡されますが、もし web3.js
を使っているのであればきにする必要はありません。
もしコントラクトが別のコントラクトを作りたい場合、作成されたコントラクトのソースコード(とそのバイナリ)は作成者によって把握されている必要があります。 つまり、cyclic creation dependenciesは出来ないということです。
pragma solidity >=0.4.22 <0.6.0;
contract OwnedToken {
// `TokenCreator` is a contract type that is defined below.
// It is fine to reference it as long as it is not used
// to create a new contract.
TokenCreator creator;
address owner;
bytes32 name;
// This is the constructor which registers the
// creator and the assigned name.
constructor(bytes32 _name) public {
// State variables are accessed via their name
// and not via e.g. `this.owner`. Functions can
// be accessed directly or through `this.f`,
// but the latter provides an external view
// to the function. Especially in the constructor,
// you should not access functions externally,
// because the function does not exist yet.
// See the next section for details.
owner = msg.sender;
// We do an explicit type conversion from `address`
// to `TokenCreator` and assume that the type of
// the calling contract is `TokenCreator`, there is
// no real way to check that.
creator = TokenCreator(msg.sender);
name = _name;
}
function changeName(bytes32 newName) public {
// Only the creator can alter the name --
// the comparison is possible since contracts
// are explicitly convertible to addresses.
if (msg.sender == address(creator))
name = newName;
}
function transfer(address newOwner) public {
// Only the current owner can transfer the token.
if (msg.sender != owner) return;
// We ask the creator contract if the transfer
// should proceed by using a function of the
// `TokenCreator` contract defined below. If
// the call fails (e.g. due to out-of-gas),
// the execution also fails here.
if (creator.isTokenTransferOK(owner, newOwner))
owner = newOwner;
}
}
contract TokenCreator {
function createToken(bytes32 name)
public
returns (OwnedToken tokenAddress)
{
// Create a new `Token` contract and return its address.
// From the JavaScript side, the return type is
// `address`, as this is the closest type available in
// the ABI.
return new OwnedToken(name);
}
function changeName(OwnedToken tokenAddress, bytes32 name) public {
// Again, the external type of `tokenAddress` is
// simply `address`.
tokenAddress.changeName(name);
}
// Perform checks to determine if transferring a token to the
// `OwnedToken` contract should proceed
function isTokenTransferOK(address currentOwner, address newOwner)
public
pure
returns (bool ok)
{
// Check an arbitrary condition to see if transfer should proceed
return keccak256(abi.encodePacked(currentOwner, newOwner))[0] == 0x7f;
}
}
Visibility and Getters¶
Solidityは2しゅるのファンクションコール(実際のEVMコールを作らないinternalのもの("message call"とも呼ばれます)とEVMコールを作るexternalのもの)があるので、ファンクションと状態変数に対して4種類の可視性があります。
ファンクションは external
、public
、internal
、private
のいずれかを指定しなければいけません。状態変数には、external
は使えません。
external
:- externalのファンクションはコントラクトインターフェースの一部です。つまり、他のコントラクトからトランザクションを通じて呼び出すことができます。あるexternalのファンクション
f
はコントラクト内部では呼び出せません(f()
は動きませんが、this.f()
は動作します)。 大きい配列を受け取る時、externalファンクションは場合によってより効率が良くなります。 public
:- externalのファンクションはコントラクトインターフェースの一部で、内部でも呼び出せますし、もしくはメッセージを通じて呼び出せます。publicの状態変数は自動的にgetter(下記参照)を生成します。
internal
:- internalのファンクションと状態変数は
this
を使わずに、コントラクト内部でのみアクセスできます(現在のコントラクトからか、それを継承したコントラクトから)。 private
:- privateのファンクションと状態変数はそれが定義されたコントラクト内のみで可視で、継承したコントラクトでは使えません。
注釈
コントラクト内側は全てブロックチェーン外側から可視です。private
だけが他のコントラクトがアクセスしたり変更したりするのを防いでくれます。しかし、それでもブロックチェーンの外側から可視です。
visibility specifierは状態変数の型の後、パラメータリストとファンクションの返り値リストの間に置かれます。
pragma solidity >=0.4.16 <0.6.0;
contract C {
function f(uint a) private pure returns (uint b) { return a + 1; }
function setData(uint a) internal { data = a; }
uint public data;
}
次の例では、D
はステートストレージ内の data
の値を引き出すのに c.getData()
を呼び出すことができます。しかし、D
は f
を呼べません。コントラクト E
は C
を継承しているため、compute
を呼び出すことができます。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint private data;
function f(uint a) private pure returns(uint b) { return a + 1; }
function setData(uint a) public { data = a; }
function getData() public view returns(uint) { return data; }
function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
// This will not compile
contract D {
function readData() public {
C c = new C();
uint local = c.f(7); // error: member `f` is not visible
c.setData(3);
local = c.getData();
local = c.compute(3, 5); // error: member `compute` is not visible
}
}
contract E is C {
function g() public {
C c = new C();
uint val = compute(3, 5); // access to internal member (from derived to parent contract)
}
}
Getter Functions¶
コンパイラは自動的に public の状態変数のgetterを生成します。下のコントラクトでは、コンパイラは data
という引数を取らず、uint
型の状態変数 data
の値を返します。 状態変数は宣言された時に初期化することができます。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint public data = 42;
}
contract Caller {
C c = new C();
function f() public view returns (uint) {
return c.data();
}
}
getterはexternalの可視性を持っています。ある記号が内部的にアクセス(this.
なし)されたら、それは状態変数と評価されます。もし外部的にアクセス(this.
あり)されたら、それはファンクションと評価されます。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint public data;
function x() public returns (uint) {
data = 3; // internal access
return this.data(); // external access
}
}
もし public
の配列型の状態変数を持っていたら、getterではその配列の1要素しか取り出すことができません。配列全体を返したときにガスが高くならない様にこの仕組みはあります。どの要素を返すか、例えば data(0)
の様に引数を使って指定することができます。もし配列全体を返したい場合は、ファンクションを作る必要があります。例えば:
pragma solidity >=0.4.0 <0.6.0;
contract arrayExample {
// public state variable
uint[] public myArray;
// Getter function generated by the compiler
/*
function myArray(uint i) returns (uint) {
return myArray[i];
}
*/
// function that returns entire array
function getArray() returns (uint[] memory) {
return myArray;
}
}
ここで、配列全体を取り出すために、1回の呼び出しで1つの要素を返す myArray(i)
の代わりに、getArray()
を使うことができます。
次の例はもっと複雑です。
pragma solidity >=0.4.0 <0.6.0;
contract Complex {
struct Data {
uint a;
bytes3 b;
mapping (uint => uint) map;
}
mapping (uint => mapping(bool => Data[])) public data;
}
これは次の形のファンクションを生成します。マッピング用のキーを渡す良い方法がないので、構造体のマッピングは省略します。
function data(uint arg1, bool arg2, uint arg3) public returns (uint a, bytes3 b) {
a = data[arg1][arg2][arg3].a;
b = data[arg1][arg2][arg3].b;
}
Function Modifiers¶
modifierはファンクションの挙動を簡単に変えるのに使うことができます。例えば、ファンクションの実行前に自動的にある条件をチェックできます。modifierは継承可能なコントラクトのプロパティで、継承されたコントラクトによってオーバーライドされるかもしれません。
pragma solidity ^0.5.0;
contract owned {
constructor() public { owner = msg.sender; }
address payable owner;
// This contract only defines a modifier but does not use
// it: it will be used in derived contracts.
// The function body is inserted where the special symbol
// `_;` in the definition of a modifier appears.
// This means that if the owner calls this function, the
// function is executed and otherwise, an exception is
// thrown.
modifier onlyOwner {
require(
msg.sender == owner,
"Only owner can call this function."
);
_;
}
}
contract mortal is owned {
// This contract inherits the `onlyOwner` modifier from
// `owned` and applies it to the `close` function, which
// causes that calls to `close` only have an effect if
// they are made by the stored owner.
function close() public onlyOwner {
selfdestruct(owner);
}
}
contract priced {
// Modifiers can receive arguments:
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) public { price = initialPrice; }
// It is important to also provide the
// `payable` keyword here, otherwise the function will
// automatically reject all Ether sent to it.
function register() public payable costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint _price) public onlyOwner {
price = _price;
}
}
contract Mutex {
bool locked;
modifier noReentrancy() {
require(
!locked,
"Reentrant call."
);
locked = true;
_;
locked = false;
}
/// This function is protected by a mutex, which means that
/// reentrant calls from within `msg.sender.call` cannot call `f` again.
/// The `return 7` statement assigns 7 to the return value but still
/// executes the statement `locked = false` in the modifier.
function f() public noReentrancy returns (uint) {
(bool success,) = msg.sender.call("");
require(success);
return 7;
}
}
複数のmodifierはホワイトスペースで分けられたリストで明記されあるファンクションに適用されます。書かれた順番に評価されます。
警告
以前のSolidityでは return
はmodifierを持つファンクションの中では異なった挙動をしていました。
modifierもしくはファンクション本体からの明示的なreturnは現在のmodifierもしくはファンクション本体からしか出ません。返ってきた変数は割り当てられ、制御フローは先に処理されたmodifierの"_"の後に続きます。
modifierの引数に任意の式が使えます。このコンテキストに置いて、ファンクションから可視の全ての記号はmodifierでも可視です。modifierで処理される記号はファンクションからは可視ではありません(オーバーライドで変わってしまう可能性があるため)。
Constant State Variables¶
状態変数は constant
として宣言することができます。この場合、コンパイル時に定数となる式で値が割り当てされている必要があります。ストレージ、ブロックチェーンデータ (例: now
、address(this).balance
、block.number
)、
実行データ (msg.value
、gasleft()
) にアクセスする式、もしくは外部のコントラクトを呼び出す式は使えません。メモリ位置の変更に影響があるかもしれない式は使えますが、メモリのオブジェクトに影響があるかもしれない式は使えません。組み込みファンクションの keccak256
、sha256
、ripemd160
、ecrecover
、addmod
、mulmod
は使用可能です( keccak256
の例外は外部コントラクトを呼び出しますが)。
メモリ位置への影響を許容する理由としては、lookup-tablesの様な複雑なオブジェクトの作成が可能であるべきだからです。 ただ、この機能はまだ完全に使用可能というわけではありません。
コンパイラはこの変数のためのストレージスロットを保持しません。結果は全て各定数式で置き換えられます(オプティマイザによって1つの値に計算されるかもしれません)。
全ての型の定数がここで実行される訳ではありません。値型と文字列だけサポートされています。
pragma solidity >=0.4.0 <0.6.0;
contract C {
uint constant x = 32**22 + 8;
string constant text = "abc";
bytes32 constant myHash = keccak256("abc");
}
Functions¶
Function Parameters and Return Variables¶
JavaScriptに見られる様に、ファンクションは入力としてパラメータをとります。JavaScriptやCと違い、ファンクションは任意の数の値を出力できます。
Function Parameters¶
ファンクションのパラメータは変数と同じ様に宣言されます。使われなかったパラメータは省略されます。
例えば、もし2つの整数でexternal callをしたい場合、次の様にできます:
pragma solidity >=0.4.16 <0.6.0;
contract Simple {
uint sum;
function taker(uint _a, uint _b) public {
sum = _a + _b;
}
}
ファンクションパラメータはローカル変数としても使えますし、その値を割り当てすることもできます。
注釈
external function は入力パラメータとして多次元配列を受け入れません。この機能に関しては、ソースファイルに pragma experimental ABIEncoderV2;
を追加して、新しい実験的な ABIEncoderV2
機能を有効にすれば使うことができます。
internal function はこの機能を有効にしなくても多次元配列を使うことができます。
Return Variables¶
ファンクションの返り値は returns
キーワードの後、同じシンタックスで宣言されます。
例えば2つの結果が欲しい時: ファンクションパラメータとして渡された2つの整数の和と積が欲しい時、次の様に書けます:
pragma solidity >=0.4.16 <0.6.0;
contract Simple {
function arithmetic(uint _a, uint _b)
public
pure
returns (uint o_sum, uint o_product)
{
o_sum = _a + _b;
o_product = _a * _b;
}
}
返り値の名前は省略可能です。返り値は他のローカル変数として使えます。返り値は default value で初期化されており、明示的に値をセットしない限りその値を持ちます。
明示的に変数に値を割り当て、return;
を使うか、直接 return
に返り値(1つもしくは multiple ones )を入れるかいずれかが可能です:
pragma solidity >=0.4.16 <0.6.0;
contract Simple {
function arithmetic(uint _a, uint _b)
public
pure
returns (uint o_sum, uint o_product)
{
return (_a + _b, _a * _b);
}
}
これは返り値を変数に割り当てて return;
を使って返す方法と結果は同じです。
注釈
internalでないファンクションからいくつかの型は返すことはできません。特に多次元動的配列と構造体です。ソースファイルに pragma experimental
ABIEncoderV2;
を加えて新しい実験的な ABIEncoderV2
機能を有効にすればもっと他の型も使える様になります。しかし、mapping
型はそれでも1つのコントラクト内でしか扱うことができず、転送することはできません。
Returning Multiple Values¶
ファンクションが複数の返り値の型を持つ時、return (v0, v1, ..., vn)
という宣言が複数の型を返すのに使用されます。
要素の数は返す値の数と同じでなければいけません。
View Functions¶
ステートを変えない場合ファンクションは view
を宣言できます。
注釈
もしコンパイラのEVMターゲットかByzantiumより新しい場合、EVM実行の一部としてステートの変更をさせないopcode STATICCALL
が view
ファンクションに使われます。ライブラリの view
ファンクションには DELEGATECALL
が使用されます。なぜなら、DELEGATECALL
と STATICCALL
が一緒になったものは存在しないからです。つまり、ライブラリの view
ファンクションはステートの変更を妨げるランタイムチェックを持っていないということです。ライブラリのコードは普通はコンパイル時に既知であり、static checkerがコンパイル時にチェックするため、これはセキュリティ的に問題ないはずです。
下記のリストはステートを変更すると考えられます。
- 状態変数を書く。
- イベントのemit。
- 他のコントラクトを作る。
selfdestruct
を使う- callを通じてEtherを送る。
view
やpure
の付いていないファンクションを呼び出す。- 低レベルcallを使う。
- あるopcodeの入ったインラインアセンブリを使う。
pragma solidity ^0.5.0;
contract C {
function f(uint a, uint b) public view returns (uint) {
return a * (b + 42) + now;
}
}
注釈
ファンクションで constant
は view
のエイリアスとして使われていましたが、バージョン0.5.0でドロップされました。
注釈
getterメソッドは自動的に view
がつきます。
注釈
バージョン0.5.0以前ではコンパイラは view
ファンクションに STATICCALL
を使っていませんでした。
これは、無効な明示的型変換を使うことで、view
ファンクションでのステートの変更を可能にしていました。
STATICCALL
を view
ファンクションに使うことで、EVM上ではステートの変更を行うことができなくなりました。
Pure Functions¶
何も読まない、ステートも変更しない場合、ファンクションで pure
を宣言できます。
注釈
コンパイラのEVMターゲットがByzantium以降であれば、opcode STATICCALL
が使えます。ステートが読まれないかは保証しませんが、少なくともステートが変更されていないことは保証されます。
ステートを変更する上記のリストに加えて、下記はステートを読み込むとされる処理のリストです。
- 状態変数から読み込む。
address(this).balance
もしくは<address>.balance
にアクセスする。block
、tx
、msg
(msg.sig
とmsg.data
は除く)のどれかにアクセスする。pure
が付いていないファンクションを呼び出す。- あるopcodeの入ったインラインアセンブリを使う。
pragma solidity ^0.5.0;
contract C {
function f(uint a, uint b) public pure returns (uint) {
return a * (b + 42);
}
}
Pureファンクションは エラーが起きた 時に、潜在的なステートの変更を元に戻すため revert() と require() を使えます。
ステートを元に戻すのは"ステートの変更"とは見なされません。
なぜなら、view
もしくは pure
がないコードで以前に作られたステートへの変更だけがrevertされていたためです。さらに、そのコードは revert
をキャッチし、受け渡ししないオプションがあります。
この挙動は STATICCALL
opcodeにも合っています。
警告
EVMレベルではファンクションがステートを読み込むことを止めることはできません。できるのは書き込みを止めることだけです( view
がEVMレベルで強制されますが、pure
はされません)。
注釈
バージョン0.5.0以前ではコンパイラは pure
ファンクションに STATICCALL
を使っていませんでした。
これは、無効な明示的型変換を使うことで、view
ファンクションでのステートの変更を可能にしていました。
STATICCALL
を pure
ファンクションに使うことで、EVM上ではステートの変更を行うことができなくなりました。
注釈
バージョン0.4.17以前では、コンパイラは pure
にステートを読まなせないということを強制していませんでした。
コンパイル時の型チェックで、コントラクト型間での無効な明示的変換を避けることができます。なぜなら、コンパイラがそのタイプのコントラクトはステートを変える操作をしないと証明するからです。ただ、ランタイム時に呼ばれるコントラクトに関しては実際にそのタイプかどうかはチェックしません。
Fallback Function¶
コントラクトは1つだけ名前の付いていないファンクションを持つことができます。そのファンクションは引数を持てず、何も返せません。そして可視性は external
である必要があります。
もし、他のファンクションが与えられたファンクションの識別子になかった場合(もしくは何のデータも渡されなかった場合)、そのコントラクトが呼ばれた時に実行されます。
さらに、このファンクションはコントラクトが(データなしの)Etherを受け取った時は実行されます。Etherを受け取って、コントラクトのトータルバランスにそれを追加するにはフォールバックファンクションは payable
でなければいけません。もしそのようなファンクションがない場合、コントラクトは通常のトランザクションを通じてEtherを受け取れず、例外を投げます。
最悪の場合、フォールバックファンクションは2300ガスだけを利用可能(例えば send か transfer を使うのに)とします。基本的なログの機能以外に他の演算のための余力をほとんど残しません。下記の演算は固定で2300ガス以上使う演算です。
- ストレージに書き込む
- コントラクトを作る
- ガスを多く使う外部ファンクションを呼び出す
- Etherを送る
他のファンクションのように、フォールバックファンクションは十分なガスが渡される限り、複雑な演算も実行可能です。
注釈
フォールバックファンクションは引数を持てませんが、呼び出しで供給されたペイロードを引き出すのに、msg.data
を使うことができます。
警告
呼び出し元が利用不可なファンクションを呼び出した時にもフォールバックファンクションは実行されます。Etherを受け取るためだけにフォールバックファンクションを実行したい場合、不正な呼び出しを防ぐために require(msg.data.length == 0)
を追加した方が良いでしょう。
警告
Etherを直接受け取る(ファンクションコール、つまり send
か transfer
を伴わない)が、フォールバックファンクションを定義しないコントラクトは例外を投げ、Etherを送り返します(Solidityバージョン0.4.0以前では違いました)。そのため、コントラクトでEtherを受け取りたい場合、payableのフォールバックファンクションを実装しなければいけません。
警告
payableフォールバックファンクションがないコントラクトは coinbase transaction (もしくは miner block reward)として、もしくは selfdestruct
の送り先としてEtherを受け取ることができます。
コントラクトはそのようなEtherの送金に関して何の反応もできないため、それを拒否することもできません。 これはEVMの設計であるため、Solidityではどうにもできません。
これは、address(this).balance
がコントラクト内で処理された手動の会計処理の合計より高くなりうることを意味しています(フォールバックファンクションでアプデートされるカウンタを持っているということです)。
pragma solidity ^0.5.0;
contract Test {
// This function is called for all messages sent to
// this contract (there is no other function).
// Sending Ether to this contract will cause an exception,
// because the fallback function does not have the `payable`
// modifier.
function() external { x = 1; }
uint x;
}
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
function() external payable { }
}
contract Caller {
function callTest(Test test) public returns (bool) {
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1.
// address(test) will not allow to call ``send`` directly, since ``test`` has no payable
// fallback function. It has to be converted to the ``address payable`` type via an
// intermediate conversion to ``uint160`` to even allow calling ``send`` on it.
address payable testPayable = address(uint160(address(test)));
// If someone sends ether to that contract,
// the transfer will fail, i.e. this returns false here.
return testPayable.send(2 ether);
}
}
Function Overloading¶
コントラクトは異なるパラメータを持つ、同じ名前のファンクションを複数持つことができます。
このプロセスは"オーバーロード"といわれ、継承されたファンクションにも適用されます。
コントラクト A
のスコープに入っているファンクション f
のオーバーロードの例を下記に示します。
pragma solidity >=0.4.16 <0.6.0;
contract A {
function f(uint _in) public pure returns (uint out) {
out = _in;
}
function f(uint _in, bool _really) public pure returns (uint out) {
if (_really)
out = _in;
}
}
オーバーロードされたファンクションは外部インターフェースの中にもあります。もし2つの外部的に可視なファンクションが外部としての型ではなく、Solidityの型として異なる場合エラーになります。
pragma solidity >=0.4.16 <0.6.0;
// This will not compile
contract A {
function f(B _in) public pure returns (B out) {
out = _in;
}
function f(address _in) public pure returns (address out) {
out = _in;
}
}
contract B {
}
両方の f
ファンクションはABIとしてはアドレス型を受け入れてオーバーロードしますが、Solidity内では違うものとして考えられます。
Overload resolution and Argument matching¶
オーバーロードされたファンクションは、現在のスコープ内のファンクションの宣言をファンクションコール内で渡された引数に合わせることによって選択されます。 全ての引数が暗示的に期待する型に変換できる場合、ファンクションはオーバーロードの候補として選ばれます。もし候補がなければ、そのresolutionは失敗します。
注釈
返ってくるパラメータはオーバーロードresolutionに考慮されません。
pragma solidity >=0.4.16 <0.6.0;
contract A {
function f(uint8 _in) public pure returns (uint8 out) {
out = _in;
}
function f(uint256 _in) public pure returns (uint256 out) {
out = _in;
}
}
50
uint8
と uint256
どちらにも暗示的に変換できるため、f(50)
の呼び出しは型エラーを生成します。
一方で、256
は暗示的に uint8
に変換できないため f(256)
は f(uint256)
オーバーロードします。
Events¶
SolidityのイベントでEVMのログ機能からデータを抽出します。アプリケーションはEthereumクライアントのRPCインターフェースを通じてイベントをsubscribeします。
イベントは継承可能なコントラクトのメンバです。イベントを呼び出すと、トランザクションのログ内に引数を保存します(ブロックチェーン上の特別なデータ構造です)。これらのログはコントラクトアドレスに結びつき、ブロックにアクセスできる限りそこに保存されます(FrontierとHomesteadからは永久にアクセス出来ますが、Serenityで変わる可能性があります)。ログとイベントデータはコントラクトからはアクセス出来ません(そのイベントを作ったコントラクトからもです)。
simple payment verification (SPV)にログをリクエスト出来るので、もし外部からその様なverificationでコントラクトを作った場合、そのSPVからその様なログが実際にブロックチェーン上に存在するかチェックすることができます。コントラクトは直近256ブロック分のハッシュしか見れないので、ブロックヘッダを渡す必要があります。
最大3つのパラメータに indexed
属性を追加することができます。それはログのデータの代わりに "topics" で知られる特別なデータ構造を追加します。インデックスされた引数として配列(string
と ``bytes``を含む)を使う場合、Keccak-256ハッシュをtopicとして代わりに保存します。なぜならtopic一つの単語(32バイト)しか保存できないからです。
全ての indexed
属性が付いていないパラメータはログのデータ部に ABI-encoded されます。
topicを使うとイベントを検索することができます。例えば、あるイベントに関してブロックシーケンスをフィルターできます。イベントから出たコントラクトアドレスでもイベントをフィルターできます。
例えば以下のコードではあるアドレス値でtopicにマッチするログをフィルターするためにweb3.js subscribe("logs")
method が使われています。
var options = {
fromBlock: 0,
address: web3.eth.defaultAccount,
topics: ["0x0000000000000000000000000000000000000000000000000000000000000000", null, null]
};
web3.eth.subscribe('logs', options, function (error, result) {
if (!error)
console.log(result);
})
.on("data", function (log) {
console.log(log);
})
.on("changed", function (log) {
});
イベントを anonymous
で宣言していなければ、イベントの署名のハッシュはtopicの1つとなります。つまり、特定のanonymousイベントに関しては名前でフィルターできません。
pragma solidity >=0.4.21 <0.6.0;
contract ClientReceipt {
event Deposit(
address indexed _from,
bytes32 indexed _id,
uint _value
);
function deposit(bytes32 _id) public payable {
// Events are emitted using `emit`, followed by
// the name of the event and the arguments
// (if any) in parentheses. Any such invocation
// (even deeply nested) can be detected from
// the JavaScript API by filtering for `Deposit`.
emit Deposit(msg.sender, _id, msg.value);
}
}
下記はJavaScript APIにおける使用例です。
var abi = /* abi as generated by the compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */);
var event = clientReceipt.Deposit();
// watch for changes
event.watch(function(error, result){
// result contains non-indexed arguments and topics
// given to the `Deposit` call.
if (!error)
console.log(result);
});
// Or pass a callback to start watching immediately
var event = clientReceipt.Deposit(function(error, result) {
if (!error)
console.log(result);
});
上記の出力は以下の様になります(トリムされています)。
{
"returnValues": {
"_from": "0x1111…FFFFCCCC",
"_id": "0x50…sd5adb20",
"_value": "0x420042"
},
"raw": {
"data": "0x7f…91385",
"topics": ["0xfd4…b4ead7", "0x7f…1a91385"]
}
}
Low-Level Interface to Logs¶
log0
、log1
、log2
、log3
、log4
ファンクションを使うことで、ログ機能の低レベルインターフェースにアクセスすることが可能です。logi
は bytes32
型の i + 1
パラメータをとります。最初の引数はログのデータ部として使用され、他はtopicとして使用されます。上記のイベントコールは下記と同じ様に実行されます。
pragma solidity >=0.4.10 <0.6.0;
contract C {
function f() public payable {
uint256 _id = 0x420042;
log3(
bytes32(msg.value),
bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20),
bytes32(uint256(msg.sender)),
bytes32(_id)
);
}
}
ここで、長い16進数の値はイベントの署名である keccak256("Deposit(address,bytes32,uint256)")
と等しいです。
Additional Resources for Understanding Events¶
Inheritance¶
Solidityはポリモフィズムを含めた多重継承をサポートしています。
全てのファンクションコールは仮想です。つまりコントラクト名が明示的に与えられた場合、super
が使われた場合を除いて、最後に派生されたファンクションが呼ばれます。
あるコントラクトがある他のコントラクトを継承するとき、1つのコントラクトだけがブロックチェーン上に生成されます。全てのベースコントラクトからのコードは生成したコントラクトにコンパイルされます。
継承のシステム全般は Python にとても似ています。特に多重継承は似ていますが、いくつかの 違い もあります。
詳細は下記の例で示します。
pragma solidity ^0.5.0;
contract owned {
constructor() public { owner = msg.sender; }
address payable owner;
}
// Use `is` to derive from another contract. Derived
// contracts can access all non-private members including
// internal functions and state variables. These cannot be
// accessed externally via `this`, though.
contract mortal is owned {
function kill() public {
if (msg.sender == owner) selfdestruct(owner);
}
}
// These abstract contracts are only provided to make the
// interface known to the compiler. Note the function
// without body. If a contract does not implement all
// functions it can only be used as an interface.
contract Config {
function lookup(uint id) public returns (address adr);
}
contract NameReg {
function register(bytes32 name) public;
function unregister() public;
}
// Multiple inheritance is possible. Note that `owned` is
// also a base class of `mortal`, yet there is only a single
// instance of `owned` (as for virtual inheritance in C++).
contract named is owned, mortal {
constructor(bytes32 name) public {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).register(name);
}
// Functions can be overridden by another function with the same name and
// the same number/types of inputs. If the overriding function has different
// types of output parameters, that causes an error.
// Both local and message-based function calls take these overrides
// into account.
function kill() public {
if (msg.sender == owner) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).unregister();
// It is still possible to call a specific
// overridden function.
mortal.kill();
}
}
}
// If a constructor takes an argument, it needs to be
// provided in the header (or modifier-invocation-style at
// the constructor of the derived contract (see below)).
contract PriceFeed is owned, mortal, named("GoldFeed") {
function updateInfo(uint newInfo) public {
if (msg.sender == owner) info = newInfo;
}
function get() public view returns(uint r) { return info; }
uint info;
}
上記注目して頂きたいのですが、破壊の要求を"転送"するのに mortal.kill()
を呼んでいます。これは下記の例で見られる様に問題があります:
pragma solidity >=0.4.22 <0.6.0;
contract owned {
constructor() public { owner = msg.sender; }
address payable owner;
}
contract mortal is owned {
function kill() public {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() public { /* do cleanup 1 */ mortal.kill(); }
}
contract Base2 is mortal {
function kill() public { /* do cleanup 2 */ mortal.kill(); }
}
contract Final is Base1, Base2 {
}
Final.kill()
のコールは、最後にオーバーライドされたものとして Base2.kill
を呼び出し、このファンクションは Base1.kill
をバイパスします。なぜなら、そのファンクションは Base1
を把握していないからです。これの回避策は super
を使うことです:
pragma solidity >=0.4.22 <0.6.0;
contract owned {
constructor() public { owner = msg.sender; }
address payable owner;
}
contract mortal is owned {
function kill() public {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() public { /* do cleanup 1 */ super.kill(); }
}
contract Base2 is mortal {
function kill() public { /* do cleanup 2 */ super.kill(); }
}
contract Final is Base1, Base2 {
}
もし Base2
が super
のファンクションを呼び出しても、単純にベースコントラクトの内の1つのこのファンクションを呼び出しません。最終的な継承図の中のベースコントラクトの次のコントラクトのファンクションを呼び出します。そのため、Base1.kill()
を呼び出します(最終的な継承の順番は、最後に継承されたコントラクトから始まります: Final、Base2、Base1、mortal、owned)。
superを使った時に呼び出される実際のファンクションは、型が分かっていても、そのクラスのコンテキストの中ではわかりません。これは一般的な仮想メソッドの検索に似ています。
Constructors¶
コンストラクタは constructor
キーワードで宣言され、コントラクト生成時に実行される任意のファンクションで、コントラクトの初期化コードを実行できます。
コンストラクタが実行される前に、インラインで初期化していれば状態変数はその値で初期化され、していなければ0になります。
コンストラクタが実行された後、コントラクトの最終的なコードがブロックチェーンにデプロイされます。コードのデプロイはコードの長さに比例して追加のガスコストがかかります。 このコードはpublicインターフェースの一部でありファンクション全てと、ファンクションコールを通じてアクセスできるファンクションを含んでいます。 このコードはコンストラクタのコードと、コンストラクタからのみ呼ばれるinternalのファンクションは含んでいません。
コンストラクタは public
か internal
です。もし、コンストラクタがなかったら、コントラクトはデフォルトのコンストラクタ( constructor() public {}
と等価の)を想定します。例えば:
pragma solidity ^0.5.0;
contract A {
uint public a;
constructor(uint _a) internal {
a = _a;
}
}
contract B is A(1) {
constructor() public {}
}
コンストラクタを internal
でセットすると、そのコントラクトは abstract になります。
警告
0.4.22以前ではコンストラクタはコントラクトと同じ名前のファンクションとして定義されていました。このシンタックスは非推奨となり、バージョン0.5.0で使えなくなりました。
Arguments for Base Constructors¶
全てのベースコントラクトのコンストラクタは下記で説明される線形ルールに則り呼び出されます。もしベースコンストラクタが引数を持っていたら、継承したコントラクトはその全てを指定する必要があります。 2通りの方法でできます:
pragma solidity >=0.4.22 <0.6.0;
contract Base {
uint x;
constructor(uint _x) public { x = _x; }
}
// Either directly specify in the inheritance list...
contract Derived1 is Base(7) {
constructor() public {}
}
// or through a "modifier" of the derived constructor.
contract Derived2 is Base {
constructor(uint _y) Base(_y * _y) public {}
}
1つ目の方法は直接継承のリストに入れることです(is Base(7)
)。もう1つの方法は継承したコンストラクタの一部としてmodifierを呼び出します(Base(_y * _y)
)。もしコンストラクタの引数が定数で、コントラクトの挙動を決めるもしくは表現するものである場合、最初の方法の方が便利です。もしベースのコンストラクタの引数が継承したコントラクトによるのであれば、2つ目の方法を使う必要があります。引数は継承のリスト、もしくは継承したコンストラクタのmodifierスタイルで与えられる必要があります。
両方で引数を指定するとエラーとなります。
もし継承したコントラクトがベースコントラクトのコンストラクタに対する引数を決めなかった場合、そのコントラクトは抽象コントラクトになります。
Multiple Inheritance and Linearization¶
多重継承ができる言語はいくつかの問題を扱わなければいけません。1つは Diamond Problem です。SolidityはPythonに似ていて、"C3 Linearization"を使っており、ベースクラスのdirected acyclic graph (DAG)で特定の順番にさせています。これはmonotonicityの理想的な性質を実現していますが、いくつかの継承図を許可していません。
特に、is
で与えられたベースクラスの順番が重要です。直のベースコントラクトを"一番ベースになるもの"から"最後に継承されるもの"の順で並べなければいけません。
これはPythonとは逆の順序であることに気をつけて下さい。
これを説明する別のシンプルな方法は、異なるコントラクトで何度か定義されたファンクションが呼ばれる時、ベースコントラクトは縦型探索で右から左に調べて(Pythonだと左から右)、最初にマッチしたところで止まります。もしすでにベースコントラクトが検索済みだった場合、それはスキップされます。
下記のコードでは、Solidityは"Linearization of inheritance graph impossible"というエラーを出します。
pragma solidity >=0.4.0 <0.6.0;
contract X {}
contract A is X {}
// This will not compile
contract C is A, X {}
この理由は、A
をオーバーライドするのに C
は X
をリクエストしましたが、A
自体は X
をオーバーライドしようとしたので、矛盾が生まれたことです。
Inheriting Different Kinds of Members of the Same Name¶
継承の結果、同じ名前のファンクションとmodifierがあるコントラクトになった場合、エラーになります。 このエラーは同じイベント名とmodifier名、同じイベント名とファンクション名でも起きます。 例外として、状態変数のgetterはpublicファンクションをオーバーライドできます。
Abstract Contracts¶
アブストラクトとなるコントラクトは少なくとも1つのファンクションは下記の様に実行部がないものを持っています(ファンクションのヘッダーが ;
で終わっていることを確認して下さい):
pragma solidity >=0.4.0 <0.6.0;
contract Feline {
function utterance() public returns (bytes32);
}
その様なコントラクトはコンパイルできません(実行できるファンクションが一緒にあったとしても)が、ベースコントラクトとして使用することができます:
pragma solidity >=0.4.0 <0.6.0;
contract Feline {
function utterance() public returns (bytes32);
}
contract Cat is Feline {
function utterance() public returns (bytes32) { return "miaow"; }
}
もしあるコントラクトがアブストラクトを継承し、全ての非実行のファンクションをオーバーライドした上で、実行しなかった場合、そのコントラクト自体がアブストラクトになります。
シンタックスはとても似ていますが、実行しないファンクションは Function Type とは違うということに注意してください。
実行しないファンクションの例(ファンクションの宣言)です:
function foo(address) external returns (address);
ファンクション型の例です(変数の型が function
である変数の宣言):
function(address) external returns (address) foo;
アブストラクトコントラクトは、より良い拡張性を持つ実装や、自己文書化、Template method の様なファシリテーションパターン、コード複製の削除を、コントラクトの定義から切り離します。 アブストラクトコントラクトはインターフェースに置いてメソッド定義が便利なのと同じ様に便利です。これがアブストラクトコントラクトの作成者が「自分の子は全てこのメソッドを実行する」と言うための方法です。
Interfaces¶
インターフェースはアブストラクトコントラクトに似ていますが、どんなファンクションも実行することはできません。他の制限がこちらです。
- 他のコントラクやインターフェースを継承できません。
- 全ての宣言されたファンクションはexternalでなければいけません。
- コンストラクタは使えません。
- 状態変数は宣言できません。
将来的にはこの制限のいくつかは撤廃されるかもしれません。
インターフェースは基本的にコントラクトABIが表せるものに制限され、ABIとインターフェース間の変換は、どんな情報の喪失もなく行われるはずです。
インターフェースはinterfaceというキーワードで表されます。
pragma solidity ^0.5.0;
interface Token {
enum TokenType { Fungible, NonFungible }
struct Coin { string obverse; string reverse; }
function transfer(address recipient, uint amount) external;
}
コントラクトは他のコントラクトを継承するようにインターフェースを継承できます。
インターフェース内で定義された型と他のコントラクトの様な構造は他のコントラクトからアクセスできます: Token.TokenType
もしくは Token.Coin
Libraries¶
ライブラリはコントラクトに似ていますが、ある特定のアドレスに一度だけデプロイされ、DELEGATECALL
(Homesteadまでは CALLCODE
)を使って、そのコードを再利用するのが目的です。つまり、ライブラリのファンクションが呼び出されたら、呼び出したコントラクトのコンテキストで実行されます。this
は呼び出したコントラクトを指しますし、特に呼び出し元のコントラクトのストレージもアクセス可能です。ライブラリは独立したソースコードなので、状態変数が明示的に渡された場合、呼び出したコントラクトの状態変数にアクセスできます(そうでないと名前をつけられません)。
ライブラリのファンクションはステートを変更しない場合( view
もしくは pure
の場合)、直接呼び出すことしかできません(つまり DELEGATECALL
を使わない)。なぜなら、ライブラリはステートレスと想定されているからです。つまり、ライブラリを破棄することはできません。
注釈
バージョン0.4.20までは、Solidityのタイプシステムを迂回することで、ライブラリの破棄が可能でした。
そのバージョンから、ライブラリがステートを変更するファンクションを直接呼び出す mechanism を導入しました(つまり DELEGATECALL
を使わない)。
ライブラリはコントラクトが使う暗示的なベースコントラクトと見做すことができます。ライブラリは継承の階層の中で明示的に可視ではないですが、ライブラリのファンクションを呼ぶのは、明示的なベースファンクションを呼ぶ様なものです( L
がライブラリの名前の時に L.f()
)。さらに、ライブラリの internal
ファンクションはまるでライブラリがベースコントラクトであるかの様に、全てのコントラクトからアクセスできます。もちろんinternalのファンクションを呼び出すにはinternalの呼び出しのルールに従う必要があります。それは全てのinternalタイプは受け渡されて、メモリに保存 された型は参照として渡され、コピーされないということです。
EVMでこれを実現するために、internalのライブラリファンクションのコードとその中から呼ばれるファンクションはコンパイル時に呼び出し元のコントラクトに入り、DELEGATECALL
の代わりに通常の JUMP
コールが使用されます。
次の例ではどの様にライブラリを使うかを説明します(マニュアルメソッドに関してはより詳細な例 using for を参照ください)。
pragma solidity >=0.4.22 <0.6.0;
library Set {
// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data { mapping(uint => bool) flags; }
// Note that the first parameter is of type "storage
// reference" and thus only its storage address and not
// its contents is passed as part of the call. This is a
// special feature of library functions. It is idiomatic
// to call the first parameter `self`, if the function can
// be seen as a method of that object.
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
}
contract C {
Set.Data knownValues;
function register(uint value) public {
// The library functions can be called without a
// specific instance of the library, since the
// "instance" will be the current contract.
require(Set.insert(knownValues, value));
}
// In this contract, we can also directly access knownValues.flags, if we want.
}
もちろん、ライブラリを使うのにこの方法に従う必要はありません。構造体型を定義しなくても使えます。ファンクションはストレージの参照型を使わなくても動きますし、どこでも複数のストレージの参照型を持つことができます。
Set.contains
、Set.insert
、Set.remove
の呼び出しは外部のコントラクト、ライブラリの呼び出し(DELEGATECALL
)としてコンパイルされます。もしライブラリを使うなら、1つの実際のexternalのファンクションコールが実行されることを覚えておいてください。msg.sender
、msg.value
、this
はその値をこのコール中に保持します( CALLCODE
、msg.sender
、msg.value
の使用方法が変わったため、Homestead以前で有効です)。
以下の例では、externalファンクションコールのオーバーヘッドなしでカスタム型を実行するために、メモリに保存される型 とライブラリのinternalファンクションをどの様に使用するか示しています。
pragma solidity >=0.4.16 <0.6.0;
library BigInt {
struct bigint {
uint[] limbs;
}
function fromUint(uint x) internal pure returns (bigint memory r) {
r.limbs = new uint[](1);
r.limbs[0] = x;
}
function add(bigint memory _a, bigint memory _b) internal pure returns (bigint memory r) {
r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length));
uint carry = 0;
for (uint i = 0; i < r.limbs.length; ++i) {
uint a = limb(_a, i);
uint b = limb(_b, i);
r.limbs[i] = a + b + carry;
if (a + b < a || (a + b == uint(-1) && carry > 0))
carry = 1;
else
carry = 0;
}
if (carry > 0) {
// too bad, we have to add a limb
uint[] memory newLimbs = new uint[](r.limbs.length + 1);
uint i;
for (i = 0; i < r.limbs.length; ++i)
newLimbs[i] = r.limbs[i];
newLimbs[i] = carry;
r.limbs = newLimbs;
}
}
function limb(bigint memory _a, uint _limb) internal pure returns (uint) {
return _limb < _a.limbs.length ? _a.limbs[_limb] : 0;
}
function max(uint a, uint b) private pure returns (uint) {
return a > b ? a : b;
}
}
contract C {
using BigInt for BigInt.bigint;
function f() public pure {
BigInt.bigint memory x = BigInt.fromUint(7);
BigInt.bigint memory y = BigInt.fromUint(uint(-1));
BigInt.bigint memory z = x.add(y);
assert(z.limb(1) > 0);
}
}
ライブラリがどこにデプロイされるかコンパイラは分からないので、アドレスはlinkerで最後のバイトコードに入れられる必要があります(リンキングのためのコマンドラインコンパイラの使い方は Using the Commandline Compiler を参照下さい)。もしアドレスが引数としてコンパイラに渡されない場合、コンパイラの16進数コードは __Set______
(
Set
はライブラリの名前) のプレースホルダを含みます。ライブラリコントラクトのアドレスの16進数エンコーディングによって、アドレスは手動で40個の記号で置き換えられ、埋められます。
注釈
生成されたバイトコード上でのライブラリの手動のリンキングは推奨されません。なぜなら、36文字に制限されているからです。コントラクトが solc
の --libraries
オプションか、コンパイラにstandard-JSONインターフェースを使っているなら、libraries
キーを使ってコンパイルされている時に、コンパイラにライブラリをリンクすることをお願いした方が良いです。
コントラクトと比べた時のライブラリの制限は:
- 状態変数がありません
- 継承する、継承されることはありません
- Etherを受け取ることができません
(これらはいつか撤廃されるかもしれません)
Call Protection For Libraries¶
イントロダクションで言及した通り、ライブラリのコードが DELEGATECALL
もしくは CALLCODE
の代わりに CALL
で実行された時、view
か pure
でなければrevertします。
EVMではコントラクトが CALL
で呼ばれたかどうか直接検知する方法はありませんが、そのコードが現在"どこ"で動作しているか調べる ADDRESS
opcodeは使えます。生成されたコードは呼び出しの種類を決めるために、このアドレスとコード生成時に使われたアドレスを比較します。
特に、ライブラリのラインタイムコードはpushから始まります。それはコンパイル時に20バイトの0で構成されています。デプロイコードがまわっている時、この定数は現在のアドレスでメモリ内で置き換えられ、修正されたコードがコントラクトに保存されます。ランタイム時に、デプロイ時のアドレスが最初の定数となって、スタック上にプッシュされ、ディスパッチャーコードが現在のアドレスとその定数をviewでもpureでもないファンクションのために比較します。
Using For¶
using A for B;
という指示はライブラリのファンクション( A
というライブラリから)をどんな型(B
)にも加えるのに使うことができます。
このファンクションは最初のパラメータとして(Pythonの self
の様に)、そのファンクションを呼び出したオブジェクトを受け取ります。
using A for *;
というのは、ライブラリ A
のファンクションが どんな 型にも追加されるということです。
いずれの場合でも、最初のパラメータの型がオブジェクトの型と合わなくても、ライブラリ中の 全ての ファンクションが追加されます。ファンクションが呼ばれた時に型はチェックされ、ファンクションのoverload resolutionが実行されます。
using A for B;
は現在の全てのファンクション内も含むコントラクト内でのみ有効です。そのコントラクトが使用されている外部のコントラクトでは使えません。この指示はファンクション内でなく、コントラクト内でのみ実行可能です。
ライブラリを含めることにより、ライブラリのファンクションを含むデータタイプが追加のコードなしで使える様になります。
Libraries の例を書き換えてみます:
pragma solidity >=0.4.16 <0.6.0;
// This is the same code as before, just without comments
library Set {
struct Data { mapping(uint => bool) flags; }
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
}
contract C {
using Set for Set.Data; // this is the crucial change
Set.Data knownValues;
function register(uint value) public {
// Here, all variables of type Set.Data have
// corresponding member functions.
// The following function call is identical to
// `Set.insert(knownValues, value)`
require(knownValues.insert(value));
}
}
この様に基本型を拡張することも可能です:
pragma solidity >=0.4.16 <0.6.0;
library Search {
function indexOf(uint[] storage self, uint value)
public
view
returns (uint)
{
for (uint i = 0; i < self.length; i++)
if (self[i] == value) return i;
return uint(-1);
}
}
contract C {
using Search for uint[];
uint[] data;
function append(uint value) public {
data.push(value);
}
function replace(uint _old, uint _new) public {
// This performs the library function call
uint index = data.indexOf(_old);
if (index == uint(-1))
data.push(_new);
else
data[index] = _new;
}
}
全てのライブラリのコールは実際のEVMのファンクションコールであるということを覚えておいてください。つまり、もしメモリもしくは値型を渡す時には、self
変数であってもコピーが実行されます。ストレージ参照の変数が使われた時だけ、コピーは実行されません。
Solidity Assembly¶
SolidityはSolidityなしで使えるアセンブリ言語を定義しており、更にSolidityのソースコード内で"インラインアセンブリ"も使用可能です。このガイドではまずどの様にインラインアセンブリを使用するのか、スタンドアローンアセンブリとどう違うのか、そしてアセンブリ自体の説明をしていきます。
Inline Assembly¶
仮想マシンの様な言語のインラインアセンブリで、Solidityの宣言をインターリーブできます。これにより細かいコントールを得ることができます。特にライブラリを作って言語を便利にする時に有用です。
EVMはスタックマシンなので、正確なスタック位置の指定や正確なスタック位置のopcodeに引数を与えるのは難しいです。Solidityのインラインアセンブリはこれらの問題や、マニュアルアセンブリを書いている時に出てくる問題に対して役にたちます。
インラインアセンブリは下記の特徴があります:
- functional-style opcodes:
mul(1, add(2, 3))
- assembly-local variables:
let x := add(2, 3) let y := mload(0x40) x := add(x, y)
- 外部の変数にアクセス:
function f(uint x) public { assembly { x := sub(x, 1) } }
- ループ:
for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }
- if文:
if slt(x, 0) { x := sub(0, x) }
- switch分:
switch x case 0 { y := mul(x, 2) } default { y := 0 }
- ファンクションコール:
function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }
警告
インラインアセンブリはEVMに低レベルでアクセスする方法です。そのため、Solidityの大事な安全に関する機能やチェックをバイパスします。必要がある時のみ、またこの機能について知識がある時のみ使用する方が良いでしょう。
Syntax¶
アセンブリはコメント、リテラル、識別子をSolidityと同じ方法でパースします。そのため、普段使っている //
と /* */
は使用可能です。インラインアセンブリは assembly { ... }
で書かれており、中括弧の中では下記が使用可能です(詳細は後述のセクションを参照ください):
- リテラル 例:
0x123
、42
、"abc"
(文字列は32文字まで)- ファンクション式のopcode 例:
add(1, mlod(0))
- 変数の宣言 例:
let x := 7
,let x := add(y, 3)
orlet x
(空(0)の初期値は割り当て済み)- 識別子 (インラインアセンブリで使われたassembly-local 変数と外部のもの) 例:
add(3, x)
,sstore(x_slot, 2)
- 値の割り当て 例:
x := add(y, 3)
- ローカルの変数が内部でスコープされているブロック 例:
{ let x := 3 { let y := add(x, 1) } }
下記の機能はスタンドアローンのアセンブリでのみ使用可能です:
dup1
、swap1
、、、を介した直接的なスタックのコントロール- ダイレクトなスタックの割り当て (in "instruction style") 例:
3 =: x
- ラベル 例:
name:
- ジャンプopcode
注釈
スタンドアローンのアセンブリは後方互換性をサポートしますが、ここではドキュメント化しません。
要求しない限り、assembly { ... }
ブロックの最後に、スタックはバランスしなければいけません。もしバランスしていなかったら、コンパイラは警告を発します。
Example¶
下記の例は、別のコントラクトのコードにアクセスするライブラリのコード例で、bytes
変数にそのコードを載せています。"生のSolidity" ではこれはできません。アセンブリライブラリはSolidityという言語をより良くするために使用されます。
pragma solidity >=0.4.0 <0.6.0;
library GetCode {
function at(address _addr) public view returns (bytes memory o_code) {
assembly {
// retrieve the size of the code, this needs assembly
let size := extcodesize(_addr)
// allocate output byte array - this could also be done without assembly
// by using o_code = new bytes(size)
o_code := mload(0x40)
// new "memory end" including padding
mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
// store length in memory
mstore(o_code, size)
// actually retrieve the code, this needs assembly
extcodecopy(_addr, add(o_code, 0x20), 0, size)
}
}
}
オプティマイザが効率的なコードを生成するのに失敗した場合にもインラインアセンブリは有用です。例えば:
pragma solidity >=0.4.16 <0.6.0;
library VectorSum {
// This function is less efficient because the optimizer currently fails to
// remove the bounds checks in array access.
function sumSolidity(uint[] memory _data) public pure returns (uint o_sum) {
for (uint i = 0; i < _data.length; ++i)
o_sum += _data[i];
}
// We know that we only access the array in bounds, so we can avoid the check.
// 0x20 needs to be added to an array because the first slot contains the
// array length.
function sumAsm(uint[] memory _data) public pure returns (uint o_sum) {
for (uint i = 0; i < _data.length; ++i) {
assembly {
o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
}
}
}
// Same as above, but accomplish the entire code within inline assembly.
function sumPureAsm(uint[] memory _data) public pure returns (uint o_sum) {
assembly {
// Load the length (first 32 bytes)
let len := mload(_data)
// Skip over the length field.
//
// Keep temporary variable so it can be incremented in place.
//
// NOTE: incrementing _data would result in an unusable
// _data variable after this assembly block
let data := add(_data, 0x20)
// Iterate until the bound is not met.
for
{ let end := add(data, mul(len, 0x20)) }
lt(data, end)
{ data := add(data, 0x20) }
{
o_sum := add(o_sum, mload(data))
}
}
}
}
Opcodes¶
このドキュメントではEthereum Virtual Machineについて完全には説明しませんが、下記のリストはEVMのopcodeのリファレンスとして使用できます。
opcodeが引数をとる場合(常にスタックの上からとります)、括弧の中に引数が入ります。
引数の順番はnon-functional styleでは逆さまに入っています(後述します)。
-
がついているopcodeは何もスタック上にプッシュしません(結果も返しません)。*
がついているopcodeは特別で、他のopcodeは1つだけスタック上にプッシュします("返り値")。
F
、H
、B
、C
がついているopcodeはそれぞれFrontier、Homestead、Byzantium、Constantinople から導入されました。Constantinopleは未だプラニングの段階ですので、そのマークがついているインストラクションは無効なインストラクションの例外を投げます。
下記で、mem[a...b)
は 位置 a
から始まって、b
で終わる(bは含まない)メモリのバイトを表しており、storage[p]
は 位置 p
でのストレージの内容を表しています。
pushi
と jumpdest
のopcodeは直接は使用できません。
グラマー上、opcodeは事前に定義された識別子として表されます。
Instruction | Explanation | ||
---|---|---|---|
stop | - | F | stop execution, identical to return(0,0) |
add(x, y) | F | x + y | |
sub(x, y) | F | x - y | |
mul(x, y) | F | x * y | |
div(x, y) | F | x / y | |
sdiv(x, y) | F | x / y, for signed numbers in two's complement | |
mod(x, y) | F | x % y | |
smod(x, y) | F | x % y, for signed numbers in two's complement | |
exp(x, y) | F | x to the power of y | |
not(x) | F | ~x, every bit of x is negated | |
lt(x, y) | F | 1 if x < y, 0 otherwise | |
gt(x, y) | F | 1 if x > y, 0 otherwise | |
slt(x, y) | F | 1 if x < y, 0 otherwise, for signed numbers in two's complement | |
sgt(x, y) | F | 1 if x > y, 0 otherwise, for signed numbers in two's complement | |
eq(x, y) | F | 1 if x == y, 0 otherwise | |
iszero(x) | F | 1 if x == 0, 0 otherwise | |
and(x, y) | F | bitwise and of x and y | |
or(x, y) | F | bitwise or of x and y | |
xor(x, y) | F | bitwise xor of x and y | |
byte(n, x) | F | nth byte of x, where the most significant byte is the 0th byte | |
shl(x, y) | C | logical shift left y by x bits | |
shr(x, y) | C | logical shift right y by x bits | |
sar(x, y) | C | arithmetic shift right y by x bits | |
addmod(x, y, m) | F | (x + y) % m with arbitrary precision arithmetic | |
mulmod(x, y, m) | F | (x * y) % m with arbitrary precision arithmetic | |
signextend(i, x) | F | sign extend from (i*8+7)th bit counting from least significant | |
keccak256(p, n) | F | keccak(mem[p...(p+n))) | |
jump(label) | - | F | jump to label / code position |
jumpi(label, cond) | - | F | jump to label if cond is nonzero |
pc | F | current position in code | |
pop(x) | - | F | remove the element pushed by x |
dup1 ... dup16 | F | copy nth stack slot to the top (counting from top) | |
swap1 ... swap16 | * | F | swap topmost and nth stack slot below it |
mload(p) | F | mem[p...(p+32)) | |
mstore(p, v) | - | F | mem[p...(p+32)) := v |
mstore8(p, v) | - | F | mem[p] := v & 0xff (only modifies a single byte) |
sload(p) | F | storage[p] | |
sstore(p, v) | - | F | storage[p] := v |
msize | F | size of memory, i.e. largest accessed memory index | |
gas | F | gas still available to execution | |
address | F | address of the current contract / execution context | |
balance(a) | F | wei balance at address a | |
caller | F | call sender (excluding delegatecall ) |
|
callvalue | F | wei sent together with the current call | |
calldataload(p) | F | call data starting from position p (32 bytes) | |
calldatasize | F | size of call data in bytes | |
calldatacopy(t, f, s) | - | F | copy s bytes from calldata at position f to mem at position t |
codesize | F | size of the code of the current contract / execution context | |
codecopy(t, f, s) | - | F | copy s bytes from code at position f to mem at position t |
extcodesize(a) | F | size of the code at address a | |
extcodecopy(a, t, f, s) | - | F | like codecopy(t, f, s) but take code at address a |
returndatasize | B | size of the last returndata | |
returndatacopy(t, f, s) | - | B | copy s bytes from returndata at position f to mem at position t |
extcodehash(a) | C | code hash of address a | |
create(v, p, n) | F | create new contract with code mem[p...(p+n)) and send v wei and return the new address | |
create2(v, p, n, s) | C | create new contract with code mem[p...(p+n)) at address
keccak256(0xff . this . s . keccak256(mem[p...(p+n)))
and send v wei and return the new address, where 0xff is a
8 byte value, this is the current contract's address
as a 20 byte value and s is a big-endian 256-bit value |
|
call(g, a, v, in, insize, out, outsize) | F | call contract at address a with input mem[in...(in+insize)) providing g gas and v wei and output area mem[out...(out+outsize)) returning 0 on error (eg. out of gas) and 1 on success | |
callcode(g, a, v, in, insize, out, outsize) | F | identical to call but only use the code from a and stay
in the context of the current contract otherwise |
|
delegatecall(g, a, in, insize, out, outsize) | H | identical to callcode but also keep caller
and callvalue |
|
staticcall(g, a, in, insize, out, outsize) | B | identical to call(g, a, 0, in, insize, out, outsize) but do
not allow state modifications |
|
return(p, s) | - | F | end execution, return data mem[p...(p+s)) |
revert(p, s) | - | B | end execution, revert state changes, return data mem[p...(p+s)) |
selfdestruct(a) | - | F | end execution, destroy current contract and send funds to a |
invalid | - | F | end execution with invalid instruction |
log0(p, s) | - | F | log without topics and data mem[p...(p+s)) |
log1(p, s, t1) | - | F | log with topic t1 and data mem[p...(p+s)) |
log2(p, s, t1, t2) | - | F | log with topics t1, t2 and data mem[p...(p+s)) |
log3(p, s, t1, t2, t3) | - | F | log with topics t1, t2, t3 and data mem[p...(p+s)) |
log4(p, s, t1, t2, t3, t4) | - | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) |
origin | F | transaction sender | |
gasprice | F | gas price of the transaction | |
blockhash(b) | F | hash of block nr b - only for last 256 blocks excluding current | |
coinbase | F | current mining beneficiary | |
timestamp | F | timestamp of the current block in seconds since the epoch | |
number | F | current block number | |
difficulty | F | difficulty of the current block | |
gaslimit | F | block gas limit of the current block |
Literals¶
10進数か16進数の表記をつけることにより整数の定数を使用することができます。そして、適切な PUSHi
インストラクションが自動的に生成されます。下記のコードは2と3を足して5になり、文字列"abc"とのビット積をとります。
最終的な値はローカル変数の x
に割り当てられます。
文字列は左詰めで保存され、32バイト以下でなければいけません。
assembly { let x := and("abc", add(3, 2)) }
Functional Style¶
opcodeのシーケンスでは、あるopcodeの実際の引数が何であるか見るのが大変なことがままあります。下記の例では、位置 0x80
にあるメモリの内容に 3
が足されます。
3 0x80 mload add 0x80 mstore
Solidityのインラインアセンブリは下記のように書かれる"ファンクショナルスタイル"の表記を持っています:
mstore(0x80, add(mload(0x80), 3))
右から左に読むと、完全に同じ定数とopcodeのシーケンスとなりますが、とても読みやすいです。
もしスタックのレイアウトが気になるなら、覚えておいて欲しいのは、シンタックス的にファンクションもしくはopcodeの最初の引数はスタックの一番上に置かれるということです。
Access to External Variables, Functions and Libraries¶
Solidityの変数や他の識別子にはその名前でアクセスすることができます。
メモリに保存されている変数に関してはスタックに値ではなくアドレスをプッシュします。ストレージに保存されている変数はこれとは異なります。全てのストレージのスロットをおそらく占有しないので、"アドレス"はスロットとスロット内のバイトオフセットで構成されています。変数 x
でポイントされているスロットを読み出すには x_slot
を使用してください。バイトオフセットを読み出すには、x_offset
を使用してください。
Solidityのローカル変数は割り当て可能です。例えば:
pragma solidity >=0.4.11 <0.6.0;
contract C {
uint b;
function f(uint x) public view returns (uint r) {
assembly {
r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero
}
}
}
警告
256ビット未満の型の変数(例えば、uint64
、address
、bytes16
、byte
)にアクセスする場合、型のエンコーディングの一部ではないビットに対してどんな想定もできません。特にゼロと想定してはいけません。
安全のために、コンテキスト内で使う前に常にデータを適切にクリアしてください:
uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }
符号付の型をクリアするのに signextend
opcodeが使用できます。
Labels¶
ラベルのサポートはSolidity 0.5.0版で削除されました。 ファンクション、ループ、if文、switch文を代わりに使用してください。
Declaring Assembly-Local Variables¶
変数を宣言するのに、let
キーワードを使用することができます。letはインラインアセンブリ内でのみ、実際には現在の {...}
-block内でのみのスコープです。
let
は新しい変数のためのスタックのスロットを作り、ブロックの最後に達したら自動的に削除されます。
その変数には初期値を与える必要があります。ただの 0
でも良いですが、複雑なファンクショナルスタイルの式でも構いません。
pragma solidity >=0.4.16 <0.6.0;
contract C {
function f(uint x) public view returns (uint b) {
assembly {
let v := add(x, 1)
mstore(0x80, v)
{
let y := add(sload(v), 1)
b := y
} // y is "deallocated" here
b := add(b, v)
} // v is "deallocated" here
}
}
Assignments¶
値の割り当てはアセンブリのローカル変数とファンクションのローカル変数へ可能です。メモリやストレージの変数を割り当てる時に注意したいのは、それはポインタを変えるだけで、データを変えているわけではないということです。
1つの値に落ち着く式しか変数に割り当てることはできません。 もし複数の値を返すファンクションから受け取った値を変数に割り当てたいときは、複数の変数を用意しなければいけません。
{
let v := 0
let g := add(v, 2)
function f() -> a, b { }
let c, d := f()
}
If¶
if文は条件分岐で使用できます。 "else"部分はありません。複数の条件分岐があるのであれば、"switch"の使用を検討してください(下記参照)。
{
if eq(value, 0) { revert(0, 0) }
}
ボディには波括弧が必要です。
Switch¶
基本的な"if/else"文として、switch文が使用可能です。
ある式の値をとって、それをいくつかの定数と比較します。
条件に合う定数の分岐が選ばれます。
間違いを起こしやすいいくつかのプログラミング言語とは異なり、操作フローはある条件から次の条件へコンテニューしません。フォールバックもしくは default
というデフォルトの条件が使えます。
{
let x := 0
switch calldataload(4)
case 0 {
x := calldataload(0x24)
}
default {
x := calldataload(0x44)
}
sstore(0, div(x, 2))
}
条件のリストでは波括弧は必要ありませんが、ボディでは必要となります。
Loops¶
アセンブリは単純なforループをサポートしています。forループは初期化部分、条件、イテレーション後のパートを含むヘッダを持っています。条件はファンクショナルスタイルの式である必要がありますが、他の2つはブロックです。初期化部分で変数を宣言した場合、この変数のスコープは本体(条件、イテレーション後のパートを含む)に拡張されます。
次の例ではメモリ内のある領域の合計を計算しています。
{
let x := 0
for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } {
x := add(x, mload(i))
}
}
forループはwhileループの様に書くこともできます:単純に初期化部分とイテレーション後のパートを空にします。
{
let x := 0
let i := 0
for { } lt(i, 0x100) { } { // while(i < 0x100)
x := add(x, mload(i))
i := add(i, 0x20)
}
}
Functions¶
アセンブリは低レベルファンクションの定義もできます。 このファンクションは引数(とリターンPC)をスタックからとってきます。また、スタックに結果をプットします。ファンクションの呼び出しはファンクショナルスタイルの実行のopcodeと同様な方法に見えます。
ファンクションはどこでも定義することができて、宣言されたブロック内がスコープになります。ファンクション内では、ファンクション外で宣言されたローカル変数にはアクセスできません。明示的な return
の宣言はありません。
複数の値を返すファンクションをコールする場合、a, b := f(x)
or let a, b := f(x)
を使って、その値をタプルに割り当てなければいけません。
下記の例では二乗と乗算を使った累乗の計算をしています。
{
function power(base, exponent) -> result {
switch exponent
case 0 { result := 1 }
case 1 { result := base }
default {
result := power(mul(base, base), div(exponent, 2))
switch mod(exponent, 2)
case 1 { result := mul(base, result) }
}
}
}
Things to Avoid¶
インラインアセンブリはかなり高いレベルに見えるかもしれませんが、実際は非常に低レベルです。ファンクションコール、ループ、if文、switch文は単純なルールの書き換えで変換されます。その後アセンブラがするのはファンクショナルスタイルのopcodeを再度並び替え、変数アクセスのためのスタックハイトを数え、ブロックの最後になったらアセンブリローカル変数のためのスタックのスロットを削除するということだけです。
Conventions in Solidity¶
EVMアセンブリと異なり、Solidityは uint24
の様な256ビットより小さい型を識別できます。効率的にするために、ほとんどの算術演算はその型を256ビットの値として扱い、はみ出た分のビットは必要な時に処理します。例えば、メモリに書き込まれる直前や、比較演算がされる時です。つまり、もしインラインアセンブリ内の変数にアクセスする時には、まずマニュアルでそのはみ出たビットを処理する必要があるかもしれません。
Solidityはメモリはとてもシンプルな方法で管理しています。
メモリ内の 0x40
に"free memory pointer"があります。メモリを振り分ける時に、このポインタが示しているメモリから始めて、順にアップデートしていくだけです。
そのメモリが以前に使われていたかどうかの保証はないので、その中身がゼロであると仮定はできません。
メモリを解放したり、好きなところにメモリを割り当てる内蔵機能は付いていません。
下記はメモリの割り当てのアセンブリの例です:
function allocate(length) -> pos {
pos := mload(0x40)
mstore(0x40, add(pos, length))
}
メモリの最初の64バイトは短期間の割り当てのための"scratch space"として使うことができます。フリーメモリポインタ( 0x60
から始まる様な)の32バイト後はずっと0であるはずで、空の動的メモリ配列の初期値として使われます。
つまり、割り当て可能なメモリは 0x80
から始まり、それはフリーメモリポインタの初期値です。
Solidityのメモリ配列の要素は常に32バイトの倍数を占めています(これは byte[]
では同じですが、bytes
と string
では違います)。多次元メモリ配列はメモリ配列のポインタです。動的配列の長さは配列の最初のスロットに保存され、その他に配列の要素が続きます。
警告
静的サイズのメモリ配列は長さの領域を持っていません。しかし、今後静的配列と動的配列の互換性のために追加されるかもしれません。
Standalone Assembly¶
上記でインラインアセンブリとして紹介したアセンブリ言語はスタンドアローンとしても使用可能で、実はSolidityのコンパイラとの中間言語として使用する予定です。ここでは、いくつか達成したいことがあります:
- その中で書かれたプログラムはSolidityからコンパイラを通じて生成されたとしても読んで理解できるものであるべきです。
- アセンブリからバイトコードへの変換は可能な限り"サプライズ"を含まない様にするべきです。
- 制御フローは形式的検証や最適化をしやすくするために、簡単に検知されるべきです。
最初と最後の目標を達成するためにアセンブリでは for
ループ、if
、switch
文の様な高レベルの概念やファンクションコールが使えます。明示的な SWAP
、DUP
、JUMP
、JUMPI
を使わないアセンブリのプログラムを書くことが可能であるべきです。なぜなら初めの2つはデータフローを分かりにくくし、後の2つは制御フローを分かりにくくするからです。
さらに、mul(add(x, y), 7)
という形のファンクショナルステートメントは 7 y x add mul
の様なピュアなopcodeより好ましいです。なぜならば、最初の式の方がどっちのオペランドにどっちのopcodeを使うのか分かりやすいからです。
2つ目の目標に関しては、とても標準的な方法でより高いレベルの概念からバイトコードにコンパイルすることで達成します。 アセンブラによって行われる唯一の非ローカル演算は、ユーザー定義の識別子(ファンクション、変数など)の名前検索です。その演算ではとてもシンプルで標準的なスコープのルールに則り、また標準的な方法でスタックからのローカル変数の削除します。
Scoping: 宣言された識別子(ラベル、変数、ファンクション、アセンブリ)は宣言されたブロック内(現在のブロックの内側のネストされたブロックも含む)のみがスコープとなります。たとえスコープ内だったとしてもファンクションの垣根を超えてローカル変数にはアクセスできません。シャドーイングは使えません。 ローカル変数は宣言前には使えませんが、ファンクションやアセンブリは使用可能です。アセンブリはランタイムコードを返したり、コントラクトの作成に使われる特別なブロックです。アウターのアセンブリの識別子はサブアセンブリの中では使えません。
もし制御フローが最後のブロックを通ったら、ブロック内で宣言されたローカル変数の数だけポップの命令が挿入されます。 ローカル変数がリファレンスされる時は、コードジェネレータはスタック内でのその変数の相対的な位置を把握している必要があり、そのためいわゆるブロックハイトをトラックしています。ブロックの最後に全てのローカル変数は削除されるので、スタックハイトはブロックの前後で同じのはずです。もし同じでない場合、コンパイルは失敗します。
switch
、for
、ファンクションを使えば、手動で jump
もしくは jumpi
を使わずに複雑なコードが書けるはずです。これにより制御フローの分析がとても簡単になり、形式的検証や最適化が改善されます。
さらに、手動でのジャンプができると、スタックハイトの計算はむしろ複雑になります。全てのスタック上のローカル変数の位置は既知である必要あり、そうでなければ、ローカル変数への参照やブロックの最後に自動でスタックからローカル変数を削除する機能はちゃんと動作しません。
Example:
Solidityからアセンブリへのコンパイル例を見てみましょう。 下記のラインタイムバイトコードを考えます:
pragma solidity >=0.4.16 <0.6.0;
contract C {
function f(uint x) public pure returns (uint y) {
y = 1;
for (uint i = 0; i < x; i++)
y = 2 * y;
}
}
以下のアセンブリが生成されます:
{
mstore(0x40, 0x80) // store the "free memory pointer"
// function dispatcher
switch div(calldataload(0), exp(2, 226))
case 0xb3de648b {
let r := f(calldataload(4))
let ret := $allocate(0x20)
mstore(ret, r)
return(ret, 0x20)
}
default { revert(0, 0) }
// memory allocator
function $allocate(size) -> pos {
pos := mload(0x40)
mstore(0x40, add(pos, size))
}
// the contract function
function f(x) -> y {
y := 1
for { let i := 0 } lt(i, x) { i := add(i, 1) } {
y := mul(2, y)
}
}
}
Assembly Grammar¶
パーサのタスクは下記の通りです:
- バイトストリームをトークンストリームにし、C++スタイルのコメントを破棄します(参照ソースには特別なコメントがありますが、ここでは割愛します)。
- 下記のグラマーに従って、トークンストリームをASTにします。
- 識別子をその識別子が定義されたブロックと一緒に登録します(ASTノードへの注記)。どこから変数にアクセスできるか注意してください。
アセンブリの字句解析器はSolidityで定義されたものに従います。
ホワイトスペース(空白文字、タブ、改行)はトークンの範囲を決めるのに使用されます。コメントは標準のJavaScript/C++のコメントで、ホワイトスペースと同様に変換されます。
Grammar:
AssemblyBlock = '{' AssemblyItem* '}'
AssemblyItem =
Identifier |
AssemblyBlock |
AssemblyExpression |
AssemblyLocalDefinition |
AssemblyAssignment |
AssemblyStackAssignment |
LabelDefinition |
AssemblyIf |
AssemblySwitch |
AssemblyFunctionDefinition |
AssemblyFor |
'break' |
'continue' |
SubAssembly
AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral
AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral
Identifier = [a-zA-Z_$] [a-zA-Z_0-9]*
AssemblyCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')'
AssemblyLocalDefinition = 'let' IdentifierOrList ( ':=' AssemblyExpression )?
AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression
IdentifierOrList = Identifier | '(' IdentifierList ')'
IdentifierList = Identifier ( ',' Identifier)*
AssemblyStackAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblyIf = 'if' AssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' AssemblyExpression AssemblyCase*
( 'default' AssemblyBlock )?
AssemblyCase = 'case' AssemblyExpression AssemblyBlock
AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')'
( '->' '(' IdentifierList ')' )? AssemblyBlock
AssemblyFor = 'for' ( AssemblyBlock | AssemblyExpression )
AssemblyExpression ( AssemblyBlock | AssemblyExpression ) AssemblyBlock
SubAssembly = 'assembly' Identifier AssemblyBlock
NumberLiteral = HexNumber | DecimalNumber
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+
Miscellaneous¶
Layout of State Variables in Storage¶
静的サイズの変数(マッピングと動的サイズの配列以外のもの)はストレージ内で 0
から始めて隣接して置かれます。複数の隣接した32バイト未満のものは可能であれば下記のルールに則り、1つのストレージスロットに詰め込まれます。
- ストレージスロットの最初の要素は低い桁の方にアラインされて保存されます
- 基本型は保存するのに必要な分だけのバイトを使います。
- 基本型がストレージのスロットの余っている部分に入りきらなかった場合は、次のストレージスロットに移動します。
- 構造体と配列データは常に新しいスロットで始まり、全てのスロットを占有します(ただし、構造体もしくは配列の中身の要素はこのルールに従ってタイトに保存されます)。
継承を使用するコントラクトに関しては、一番基本的なコントラクトで始まるC3線形化されたコントラクトの順番で状態変数の順序は決まります。上記のルール内であれば、異なるコントラクトからの状態変数でも同じストレージスロットを共有できます。
警告
32バイト未満の要素を使用すると、コントラクトのガス消費量が高くなるかもしれません。これはEVMが一度に32バイトで動作するため、もし要素が32バイト未満だと、そのエレメントを32バイトから理想的なサイズに減らすために、EVMは演算をしなければならなくなるからです。
ストレージの値を使っている時のみ、縮小したサイズの引数を使うメリットがあります。それはコンパイラが複数の要素をパックして1つのストレージスロットに入れ、複数の読み込みと書き出しをくっつけて1つの演算で行うからです。もしファンクションの引数やメモリの値を使うのであれば、コンパイラがそれらの値をパックしないので、メリットはありません。
最後に、EVMのその機能を最適化させるために、ストレージ変数と
struct
の要素の順番をきっちりとパックされるように整理して下さい。例えば、ストレージ変数の宣言をuint128、uint256、uint128
ではなく、uint128、uint128、uint256
とすると、前者では3スロット使いますが、後者では2スロットだけ使います。
注釈
ストレージのポインタはライブラリに渡される可能性があるので、ストレージ内の状態変数の設計はSolidityの外部インターフェースの一部と考えてください。 つまり、このセクションで並べらrたルールの変更は言語のブレーキングチェンジと考えてください。そして、この重要性から、実行する前にしっかりと設計を考えてください。
構造体の要素と配列は明示的に与えられたかの様にお互いの後に保存されます。
Mappings and Dynamic Arrays¶
サイズが予想できないため、マッピングと動的サイズ配列はKeccak-256のハッシュ演算を値や配列のデータの始めの位置を見つけるのに使います。この始めの位置は常にフルスタックのスロットです。
マッピングもしくは動的配列自体はストレージ内のある位置 p
のスロットを上記のルール(マッピングのマッピングや配列の配列には再帰的にそのルールを適用します)に従って占有します。動的配列に関しては、その配列内の要素の数をそのスロットに保存します(バイト配列と文字列は例外です。詳しくは 下記 を参照してください)。
マッピングに関しては、スロットは使われません(しかし、二つの同じマッピングが異なるハッシュを使うので必要です)。配列のデータは keccak256(p)
に入り、マッピングのキー k
に対応する値は keccak256(k . p)
に入ります。.
は連結を意味します。もし値が再び非基本型の場合は keccak256(k . p)
のオフセットを追加することによってその場所を見つけることができます。
下記がコントラクトの例です:
pragma solidity >=0.4.0 <0.6.0;
contract C {
struct s { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => s)) data;
}
data[4][9].b
の場所は keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1
となります。
bytes
and string
¶
bytes
と string
は完全に同じようにエンコードされます。小さいバイト配列に関しては、長さとデータを同じスロットに保存します。特にデータが最大 31
バイトの場合、高い桁のバイト(左にアラインされたもの)側で保存され、一番小さい桁のバイトは length * 2` を保存します。
32
バイト以上のデータを保存している配列の場合、メインスロットは length * 2 + 1
を保存し、データは通常通り keccak256(slot)
で保存されます。つまり、短い配列と長い配列を一番下位のビットを調べることにより識別することができます。短いのはビットがセットされておらず、長いのはされています。
注釈
不正にエンコードされたスロットの操作は現在サポートされていませんが、将来的には追加されるかもしれません。
Layout in Memory¶
Solidityは4つの32バイトスロットを持ち、それは下記の様に使われる特定のバイトのレンジ(エンドポイントを含む)を持っています:
0x00
-0x3f
(64 bytes): ハッシュメソッド用のスクラッチスペース0x40
-0x5f
(32 bytes): 現在割り当てられているメモリのサイズ(フリーメモリポインタともいう)0x60
-0x7f
(32 bytes): ゼロスロット
スクラッチスペースは宣言の間で使えます(具体的にはインラインアセンブリ内)。ゼロスロットは動的メモリ配列の初期値に使われ、書き込みはされないべきです(フリーメモリポインタは最初に 0x80
を指しています)。
Solidityは常に新しいオブジェクトをフリーメモリポインタに置き、メモリはフリーになりません(将来的にはこれは変わるかもしれません)。
警告
Solidityでは64バイトより大きい一時メモリエリアが必要になる演算があるため、それはスクラッチスペースにはフィットしません。その演算はフリーメモリポインタが指し示した場所に置かれますが、その短いライフタイムを考えるとポインタはアップデートされません。 メモリはゼロになるかもしれないし、ならないかもしれません。 これらの理由によりフリーメモリがゼロになったメモリを指し示すと考えないほうが良いでしょう。
完全にゼロにされたメモリエリアに達するために msize
を使うのは良いアイデアと思えますが、フリーメモリポインタをアップデートしないで非一時的にその様なポインタを使うと、逆の結果となる可能性があります。
Layout of Call Data¶
ファンクションを呼び出すのに入力したデータは ABI specification で定義されたフォーマットになっていると考えられます。他のものでは、ABI specificationは引数を32バイトの倍数にパディングする様に要求しています。内部のファンクションコールでは異なった慣例を用いています。
コントラクトのコンストラクタ用の引数は直接コントラクトのコードの最後に追加されます。ABIエンコーディングでも同様です。コンストラクタはハードコードされたオフセットを通じて引数にアクセスします。データをコードに追加した時に変わってしまうので、codesize
opcodeは使わずにアクセスします。
Internals - Cleaning Up Variables¶
値が256ビットより短い場合、いくつかのケースでは余ったビットはクリア必要があります。 Solidityのコンパイラは、残ったビットにある不要なデータに影響を受けるかもしれない演算前にその様な残ったビットをクリアします。 例えば、メモリに値を書き込む前に、残ったビットはクリアされる必要があります。なぜなら、メモリの内容がハッシュの計算に使われたり、メッセージコールのデータとして送られる可能性があるためです。 同様によく分からない値があるかもしれないので、ストレージに値を保存する前に、残ったビットはクリアされる必要があります。
一方で、続く演算が影響をすぐに及ぼさないのであれば、ビットをクリアしません。例えば、JUMPI
命令ではゼロでない値は true
とされるので、JUMPI
用の条件として使われる前にはbooleanの値はクリアしません。
上記の設計原理に加えて、Solidityのコンパイラはデータがスタックの追加されたらそのデータをクリアします。
不正な値を削除するルールは型によって異なります。
Type | Valid Values | Invalid Values Mean |
---|---|---|
enum of n members | 0 until n - 1 | exception |
bool | 0 or 1 | 1 |
signed integers | sign-extended word | currently silently wraps; in the future exceptions will be thrown |
unsigned integers | higher bits zeroed | currently silently wraps; in the future exceptions will be thrown |
Internals - The Optimiser¶
Solidityのオプティマイザはアセンブリ上で動作するので、他の言語でも使えます。オプティマイザは命令のシーケンスを JUMPs
と JUMPDESTs
で基本的なブロックに分けます。このブロックの内側では、オプティマイザが命令を解析し、スタック、メモリ、もしくはストレージの変更を他の式を指し示している引数のリストと命令で構成されている式として記録しています。オプティマイザは"CommonSubexpressionEliminator"というコンポーネントを使用しています。それは他のタスクの中で、同じもの(入力に対して)ものを見つけて、それを1つの式クラスにまとめます。オプティマイザはまず最初に既知の式のリストから新しい式を見つけようとします。もしこれがうまく行かなかった場合、constant + constant = sum_of_constants
もしくは X * 1 = X
の様なルールに従い、式を簡略化します。これは再帰的な処理なので、もし2つ目の因数が最終的に1になる複雑な式だった場合、後者のルールが適用できます。ストレージとメモリの位置の修正は、異なると分かっていないストレージとメモリの位置の情報を消さなければいけません。もし最初に位置xに変数を書き込みし、その後に位置yに変数を書き込みをした場合、後者が前者を上書きしてしまう可能性があります。この場合yに書き込みした後はxに何が保存されているか分かりません。式x-yが0でない値だった場合、xに何が保存されているか分かります。
この処理の後、どの式が最後にスタックにのるか分かりますし、メモリとストレージの修正のリストも保持します。この情報は基本ブロックと一緒に保存され、それらをリンクさせるのに使われます。さらに、スタック、ストレージ、メモリの設定の情報は次のブロックに転送されます。もし全ての JUMP
と JUMPI
命令のターゲットを知っていれば、プログラムの完全な制御フローの図式を作ることができます。もし1つだけ分からないターゲットがあった場合(原理的にはありえます。jumpのターゲットは入力から計算されます)、未知の JUMP
のターゲットになりうるので、ブロックの入力ステートに関する全ての情報を削除する必要があります。オプティマイザが条件が定数になる JUMPI
を見つけた場合、無条件のjumpに変換します。
最終ステップとして、それぞれのブロックのコードは再生成されます。オプティマイザはブロックの最後で、スタック上の式から依存関係の図式を生成します。そしてこの図式に入っていない演算は省略します。オプティマイザはメモリとストレージの変更をオリジナルのコードで作られた順番で適用します(必要ない変更は省略します)。最後に、スタック上の正しい位置に置かれる必要がある全ての値を生成します。
これらのステップは基本ブロックに適用され、新しく生成されたコードはもしオリジナルより小さければ差し替えられます。もし基本ブロックが JUMPI
で分割され、分析中に条件文が定数と評価された場合、JUMPI
はその定数の値によって置換されます。
uint x = 7;
data[7] = 9;
if (data[x] != x + 2)
return 2;
else
return 1;
処理のはじめに命令がjumpを含んでいても、コンパイルできるコードに簡略化します。
data[7] = 9;
return 1;
Source Mappings¶
ASTアウトプットの一部として、コンパイラはASTの各ノードによって表されるソースコードの領域を提供します。これは色々な目的に使えます。例えば、ASTに基づいたエラーをレポートする静的解析ツールや、ローカル変数や用途をハイライトするデバッグツールです。
さらに、コンパイラはバイトコードから命令を生成するソースコード内の領域のマッピングも生成します。これはバイトコードレベルで動く静的解析ツールにとって重要です。デバッガー内でソースコードのどこに位置しているか表示するもしくはブレークポイントの操作にとっても重要です。
いずれのソースマッピングもソースファイルを参照するために整数識別子を使っています。
ソースファイルの識別子は output['sources'][sourceName]['id']
に保存されています。ここで、output
はJSONにパースされたstandard-jsonコンパイラインターフェースの出力です。
注釈
どのソースファイルにも関連していない命令の場合、ソースマッピングは -1
という整数識別子を割り当てます。これはコンパイラが生成したインラインアセンブリの宣言に起因するバイトコードセクションで発生する可能性があります。
AST内のソースマッピングは以下の注記を使います:
s:l:f
s
はソースファイル内の領域の始めに対するバイトオフセットで、l
はバイトでソース領域の長さを表し、f
は上記で言及したソースインデックスです。
バイトコード用のソースマッピング内のエンコードはもっと複雑です。
;
で分けられた s:l:f:j
のリストです。それぞれの要素は1つの命令に対応しており、つまり、バイトオフセットは使えませんが、命令のオフセットは使わなければいけないということです(命令のプッシュは1バイトより大きいです)。
s
、l
、f
フィールドは上記の通りで、j
は
i
、o
もしくは -
となります。それぞれ意味するのはjump命令が、ファンクションに入るかどうか、ファンクションから返ってくるかどうか、例えばループの一部としての標準的なjumpかどうか、です。
それらのソースマッピング、特にバイトコード用の、を圧縮するために、次のルールが使用されます。
- もしフィールドが空であれば、前の要素の値が使われます。
- もし、
:
がなかった場合、それに続く全てのフィールドは空であるとされます。
つまり、次のソースマッピングは同じものを表しています。
1:2:1;1:9:1;2:1:2;2:1:2;2:1:2
1:2:1;:9;2:1:2;;
Tips and Tricks¶
- 全ての要素を削除するには
delete
を使用して下さい。 - 構造体の要素としてより小さい型を使って、その型が一纏めになるようにソートして下さい。これは複数の
SSTORE
演算を1つの演算にまとめられる可能性があるので、ガスコストを低くすることができます(SSTORE
は5000もしくは20000ガスします。これが最適化です)。オプティマイザを使った状態でgas price estimatorを使ってチェックしてください! - 状態変数はpublicにしておいてください。コンパイラが自動的に getters を作ります。
- もしファンクションのはじめに入力やステートに対してチェックをたくさんかけているのであれば、Function Modifiers を使ってみて下さい。
- ストレージの構造体は1回の割り当てで初期化してください:
x = MyStruct({a: 1, b: 2});
注釈
もしストレージの構造体がプロパティで詰まっているなら、別々の割り当てで初期化して下さい: x.a = 1; x.b = 2;
。この方法の方がオプティマイザにとってストレージのアップデートが1回で済むため、値の割り当てが安くなります。
Cheatsheet¶
Order of Precedence of Operators¶
下記は演算子に対する命令の優先順位です。評価順にリスト化されています。
Precedence | Description | Operator |
---|---|---|
1 | Postfix increment and decrement | ++ , -- |
New expression | new <typename> |
|
Array subscripting | <array>[<index>] |
|
Member access | <object>.<member> |
|
Function-like call | <func>(<args...>) |
|
Parentheses | (<statement>) |
|
2 | Prefix increment and decrement | ++ , -- |
Unary minus | - |
|
Unary operations | delete |
|
Logical NOT | ! |
|
Bitwise NOT | ~ |
|
3 | Exponentiation | ** |
4 | Multiplication, division and modulo | * , / , % |
5 | Addition and subtraction | + , - |
6 | Bitwise shift operators | << , >> |
7 | Bitwise AND | & |
8 | Bitwise XOR | ^ |
9 | Bitwise OR | | |
10 | Inequality operators | < , > , <= , >= |
11 | Equality operators | == , != |
12 | Logical AND | && |
13 | Logical OR | || |
14 | Ternary operator | <conditional> ? <if-true> : <if-false> |
15 | Assignment operators | = , |= , ^= , &= , <<= ,
>>= , += , -= , *= , /= ,
%= |
16 | Comma operator | , |
Global Variables¶
abi.decode(bytes memory encodedData, (...)) returns (...)
: 与えられたデータを ABI-デコードします。カッコ内で2つ目の引数として型が与えられます。例:(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))
abi.encode(...) returns (bytes memory)
: 与えられた引数を ABI-エンコードしますabi.encodePacked(...) returns (bytes memory)
: 与えられた引数の packed encoding をします。abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)
: 与えられた引数の2つ目からを ABI-エンコードし、4バイトのセレクタを前につけますabi.encodeWithSignature(string memory signature, ...) returns (bytes memory)
:abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)
と同じですblock.coinbase
(address payable
): 現在のブロックのマイナーのアドレスblock.difficulty
(uint
): 現在のブロックのディフィカルティblock.gaslimit
(uint
): 現在のブロックのガスリミットblock.number
(uint
): 現在のブロックナンバーblock.timestamp
(uint
): 現在のブロックのタイムスタンプgasleft() returns (uint256)
: 残っているガスmsg.data
(bytes
): calldata全部msg.sender
(address payable
): 現在呼ばれているメッセージの送信者msg.value
(uint
): メッセージと一緒に送られたwei量now
(uint
): 現在のタイムスタンプ (block.timestamp
のエイリアス)tx.gasprice
(uint
): トランザクションガスプライスtx.origin
(address payable
): トランザクションの送信者(フルコールチェーン)assert(bool condition)
: もし条件文がfalse
の場合、実行を止め、状態変更を元に戻します (内部エラーに使う)require(bool condition)
: もし条件文がfalse
の場合、実行を止め、状態変更を元に戻します (不正な形式の入力や外部コンポーネントのエラーに使う)require(bool condition, string memory message)
: もし条件文がfalse
の場合、実行を止め、状態変更を元に戻します (不正な形式の入力や外部コンポーネントのエラーに使う)。エラーメッセージも出しますrevert()
: 実行を止め、状態変更を元に戻しますrevert(string memory message)
: 説明文を出し、実行を止め、状態変更を元に戻しますblockhash(uint blockNumber) returns (bytes32)
: 与えられたブロックのハッシュを返します(直帰256ブロックのみ有効です)keccak256(bytes memory) returns (bytes32)
: 入力のKeccak-256ハッシュを計算しますsha256(bytes memory) returns (bytes32)
: 入力のSHA-256ハッシュを計算しますripemd160(bytes memory) returns (bytes20)
: 入力のRIPEMD-160ハッシュを計算しますecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
: 楕円曲線の署名から公開鍵に関連したアドレスを復元する、もしくはエラーでゼロを返します。addmod(uint x, uint y, uint k) returns (uint)
: 任意の精度で(x + y) % k
の加算を行い、2**256
でラップアラウンドしません。バージョン0.5.0からはk != 0
のアサーションを行いますmulmod(uint x, uint y, uint k) returns (uint)
: 任意の精度で(x * y) % k
の加算を行い、2**256
でラップアラウンドしません。バージョン0.5.0からはk != 0
のアサーションを行いますthis
(current contract's type): 現在のコントラクト、明示的にaddress
もしくはaddress payable
に変換可能ですsuper
: 継承の階層で1つ上のコントラクトselfdestruct(address payable recipient)
: 現在のコントラクトを破壊し、与えられたaddressにファンドを送ります。<address>.balance
(uint256
): Address のバランス(Wei)<address payable>.send(uint256 amount) returns (bool)
: 与えられたWeiを Address に送ります。失敗するとfalse
を返します<address payable>.transfer(uint256 amount)
: 与えられたWeiを Address に送ります。失敗するとエラーを投げますtype(C).creationCode
(bytes memory
):
コントラクトの生成バイトコード。詳細は Type Information を参照ください
- type(C).runtimeCode
(bytes memory
):
コントラクトのランタイムバイトコード。詳細は Type Information をご覧ください
自分のコードで何をしているか把握していない限り、block.timestamp
、now
と blockhash
を乱数のソースとして信用しないでください。
タイムスタンプとブロックハッシュはある程度マイナーによって影響されます。悪意を持ったマイナーは例えばあるハッシュでカジノの支払いファンクションを呼び出し、もしお金を受け取れなかったら、また別のハッシュでそのファンクションを呼び出すことができます。
現在のブロックのタイムスタンプは最後のブロックより確実に大きい必要がありますが、保証されているのはタイムスタンプは2つの連続する標準ブロックの間であるということだけです。
注釈
ブロックハッシュはスケーラビリティの観点から全てのブロックに使うことはできません。 直近256ブロックのみアクセス可能で、他の値は全て0になります。
注釈
バージョン0.5.0では下記のエイリアスが廃止になりました。
suicide
: selfdestruct
のエイリアス
msg.gas
: gasleft
のエイリアス
block.blockhash
: blockhash
のエイリアス
sha3
: keccak256
のエイリアス
Function Visibility Specifiers¶
function myFunction() <visibility specifier> returns (bool) {
return true;
}
public
: 外部からも内部からもアクセス可能です(ストレージ/状態変数の getter function を生成します)private
: 現在のコントラクト内でのみ使えますexternal
: 外部からのみ使用可能です(ファンクションのみ)。つまりメッセージコールのみされます(this.func
を通じて)internal
: 内部でのみ使用可能です
Modifiers¶
pure
for functions: ステートの修正やアクセスができませんview
for functions: ステートの修正ができませんpayable
for functions: コールと一緒にEtherが受け取れますconstant
for state variables: 値の割り当てができません(初期化時を除く)。ストレージスロットは使いませんanonymous
for events: topicとしてイベントの署名は保存しませんindexed
for event parameters: topicとしてパラメータを保存します
Reserved Keywords¶
下記のキーワードはSolidityの予約語です。将来的にはシンタックスの一部となる可能性があります。
abstract
, after
, alias
, apply
, auto
, case
, catch
, copyof
, default
,
define
, final
, immutable
, implements
, in
, inline
, let
, macro
, match
,
mutable
, null
, of
, override
, partial
, promise
, reference
, relocatable
,
sealed
, sizeof
, static
, supports
, switch
, try
, typedef
, typeof
,
unchecked
.
Language Grammar¶
SourceUnit = (PragmaDirective | ImportDirective | ContractDefinition)*
// Pragma actually parses anything up to the trailing ';' to be fully forward-compatible.
PragmaDirective = 'pragma' Identifier ([^;]+) ';'
ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
ContractPart = StateVariableDeclaration | UsingForDeclaration
| StructDefinition | ModifierDefinition | FunctionDefinition | EventDefinition | EnumDefinition
InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )* Identifier ('=' Expression)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* ) '}'
ModifierDefinition = 'modifier' Identifier ParameterList? Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
FunctionDefinition = 'function' Identifier? ParameterList
( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' )*
( 'returns' ParameterList )? ( ';' | Block )
EventDefinition = 'event' Identifier EventParameterList 'anonymous'? ';'
EnumValue = Identifier
EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}'
ParameterList = '(' ( Parameter (',' Parameter)* )? ')'
Parameter = TypeName StorageLocation? Identifier?
EventParameterList = '(' ( EventParameter (',' EventParameter )* )? ')'
EventParameter = TypeName 'indexed'? Identifier?
FunctionTypeParameterList = '(' ( FunctionTypeParameter (',' FunctionTypeParameter )* )? ')'
FunctionTypeParameter = TypeName StorageLocation?
// semantic restriction: mappings and structs (recursively) containing mappings
// are not allowed in argument lists
VariableDeclaration = TypeName StorageLocation? Identifier
TypeName = ElementaryTypeName
| UserDefinedTypeName
| Mapping
| ArrayTypeName
| FunctionTypeName
| ( 'address' 'payable' )
UserDefinedTypeName = Identifier ( '.' Identifier )*
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
( 'returns' FunctionTypeParameterList )?
StorageLocation = 'memory' | 'storage' | 'calldata'
StateMutability = 'pure' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | EmitStatement | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
InlineAssemblyStatement = 'assembly' StringLiteral? AssemblyBlock
DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
Continue = 'continue'
Break = 'break'
Return = 'return' Expression?
Throw = 'throw'
EmitStatement = 'emit' FunctionCall
VariableDefinition = (VariableDeclaration | '(' VariableDeclaration? (',' VariableDeclaration? )* ')' ) ( '=' Expression )?
// Precedence by order (see github.com/ethereum/solidity/pull/732)
Expression
= Expression ('++' | '--')
| NewExpression
| IndexAccess
| MemberAccess
| FunctionCall
| '(' Expression ')'
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
| Expression '**' Expression
| Expression ('*' | '/' | '%') Expression
| Expression ('+' | '-') Expression
| Expression ('<<' | '>>') Expression
| Expression '&' Expression
| Expression '^' Expression
| Expression '|' Expression
| Expression ('<' | '>' | '<=' | '>=') Expression
| Expression ('==' | '!=') Expression
| Expression '&&' Expression
| Expression '||' Expression
| Expression '?' Expression ':' Expression
| Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression
| PrimaryExpression
PrimaryExpression = BooleanLiteral
| NumberLiteral
| HexLiteral
| StringLiteral
| TupleExpression
| Identifier
| ElementaryTypeNameExpression
ExpressionList = Expression ( ',' Expression )*
NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
FunctionCall = Expression '(' FunctionCallArguments ')'
FunctionCallArguments = '{' NameValueList? '}'
| ExpressionList?
NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']'
BooleanLiteral = 'true' | 'false'
NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?
NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+ ( '.' [0-9]* )? ( [eE] [0-9]+ )?
TupleExpression = '(' ( Expression? ( ',' Expression? )* )? ')'
| '[' ( Expression ( ',' Expression )* )? ']'
ElementaryTypeNameExpression = ElementaryTypeName
ElementaryTypeName = 'address' | 'bool' | 'string' | Int | Uint | Byte | Fixed | Ufixed
Int = 'int' | 'int8' | 'int16' | 'int24' | 'int32' | 'int40' | 'int48' | 'int56' | 'int64' | 'int72' | 'int80' | 'int88' | 'int96' | 'int104' | 'int112' | 'int120' | 'int128' | 'int136' | 'int144' | 'int152' | 'int160' | 'int168' | 'int176' | 'int184' | 'int192' | 'int200' | 'int208' | 'int216' | 'int224' | 'int232' | 'int240' | 'int248' | 'int256'
Uint = 'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' | 'uint56' | 'uint64' | 'uint72' | 'uint80' | 'uint88' | 'uint96' | 'uint104' | 'uint112' | 'uint120' | 'uint128' | 'uint136' | 'uint144' | 'uint152' | 'uint160' | 'uint168' | 'uint176' | 'uint184' | 'uint192' | 'uint200' | 'uint208' | 'uint216' | 'uint224' | 'uint232' | 'uint240' | 'uint248' | 'uint256'
Byte = 'byte' | 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32'
Fixed = 'fixed' | ( 'fixed' [0-9]+ 'x' [0-9]+ )
Ufixed = 'ufixed' | ( 'ufixed' [0-9]+ 'x' [0-9]+ )
AssemblyBlock = '{' AssemblyStatement* '}'
AssemblyStatement = AssemblyBlock
| AssemblyFunctionDefinition
| AssemblyVariableDeclaration
| AssemblyAssignment
| AssemblyIf
| AssemblyExpression
| AssemblySwitch
| AssemblyForLoop
| AssemblyBreakContinue
AssemblyFunctionDefinition =
'function' Identifier '(' AssemblyIdentifierList? ')'
( '->' AssemblyIdentifierList )? AssemblyBlock
AssemblyVariableDeclaration = 'let' AssemblyIdentifierList ( ':=' AssemblyExpression )?
AssemblyAssignment = AssemblyIdentifierList ':=' AssemblyExpression
AssemblyExpression = AssemblyFunctionCall | Identifier | Literal
AssemblyIf = 'if' AssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' AssemblyExpression ( Case+ AssemblyDefault? | AssemblyDefault )
AssemblyCase = 'case' Literal AssemblyBlock
AssemblyDefault = 'default' AssemblyBlock
AssemblyForLoop = 'for' AssemblyBlock AssemblyExpression AssemblyBlock AssemblyBlock
AssemblyBreakContinue = 'break' | 'continue'
AssemblyFunctionCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')'
AssemblyIdentifierList = Identifier ( ',' Identifier )*
Solidity v0.5.0 Breaking Changes¶
このセクションではSolidityバージョン0.5.0で導入された主要な変更を、変更の理由と影響あるコードをアップデートする方法と共に紹介します。 フルリストは、the release changelog を参照ください。
注釈
Solidityのバージョン0.5.0でコンパイルされたコントラクトは古いバージョンでコンパイルされたコントラクトやライブラリを再コンパイルもしくは再デプロイなしに使えます。 データロケーション、可視性、ミュータビリティを含めるようにインターフェースを変えれば十分です。下記の Interoperability With Older Contracts を参照ください。
Semantic Only Changes¶
このセクションはセマンティクス上の変更だけのリストです。そのため、潜在的には現状のコードで、新しいもしくは異なる動作をしてしまうかもしれません。
- 符号付右シフトは現在適切な算術シフトを使っています。つまり、0への丸めの代わりに負の無限大への丸めとなります。符号付、符号なしのシフトはConstantinopleで専用のopcodeになり、その間Solidityによってエミュレートされます。
do...while
ループの中のcontinue
は現在条件文にjumpします。そのような場合になる一般的な挙動です。以前はループの本体にjumpしていました。もし条件文がfalseだった場合、ループは終了します。bytes
が1つ与えられた時に、ファンクションの.call()
、.delegatecall()
、.staticcall()
はもうパディングしません。- pureとviewファンクションはEVMバージョンがByzantium以降であれば、
CALL
の代わりにSTATICCALL
を使って呼ばれます。このopcodeではEVMレベルで、状態変更ができません。 - 外部のファンクションコールと
abi.encode
で使われた時、ABIエンコーダは現在calldata(msg.data
と外部のファンクションパラメータ)のバイト配列と文字列を適切にパディングします。パディングされないエンコードしたい時は、abi.encodePacked
を使用してください。 - ABIデコーダは渡されたcalldataが短すぎるもしくは範囲外を指し示していたら、ファンクションの始めと
abi.decode()
で状態を全て元に戻します。
汚れた高次ビットは単純に無視されるということを覚えておいてください。
- Tangerine Whistleからは外部のファンクションコールで使用可能な全てのガスを転送します。
Semantic and Syntactic Changes¶
このセクションではシンタックスとセマンティクスに関連する変更を紹介します。
.call()
、.delegatecall()
、staticcall()
、keccak256()
、sha256()
、ripemd160()
は1つのbytes
引数だけを受け入れます。さらに引数はパディングされません。これは引数がどのように連結させるかをわかりやすくするためです。
全ての .call()
(and family) を .call("")
に代えてください。.call(signature, a, b, c)
の代わりに .call(abi.encodeWithSignature(signature, a, b, c))
(最後のものは値型にしか使えません)を使ってください。また、keccak256(a, b, c)
を keccak256(abi.encodePacked(a, b, c))
に代えてください。ブレーキングチェンジではありませんが、x.call(bytes4(keccak256("f(uint256)"), a, b)
から x.call(abi.encodeWithSignature("f(uint256)", a, b))
に代えることをお勧めします。
.call()
、.delegatecall()
、.staticcall()
は返ってきたデータにアクセスできるように現在(bool, bytes memory)
を返します。bool success = otherContract.call("f")
を(bool success, bytes memory data) = otherContract.call("f")
に変更して下さい。- Solidityは現在ファンクションのローカル変数に対してC99-styleのスコーピングのルールを使っています。その中で、変数は宣言された後でのみ有効で、同じもしくはネストされたスコープでのみ使えます。
for
ループの初期化ブロックの中で宣言された変数はループ内のどこでも有効です。
Explicitness Requirements¶
このセクションはコードをもっと明示的にする様な変更のリストです。 ほとんどの場合、コンパイラが忠告を出してくれます。
- 明示的なファンクションの可視性は現在必須です。可視性を特に指示していないものに関して、ファンクションとコンストラクタには
public
を追加し、fallbackとインターフェースにはexternal
を追加して下さい。 - 構造体、配列、マッピングに関して明示的なデータロケーションは現在必須です。これはファンクションのパラメータや返り値にも適用されます。例えば、
uint[] x = m_x
はuint[] storage x = m_x
に、function f(uint[][] x)
はfunction f(uint[][] memory x)
に代えてください。
ここで、memory
はデータロケーションで、場合によって storage
や calldata
に置き換えられます。external
のファンクションはデータロケーションとして calldata
が必要なことを覚えておいてください。
- 名前空間を分けるために、コントラクト型は
address
メンバをもう持っていません。そのため、address
メンバを使う前に明示的にコントラクト型の値をアドレスに変換する必要があります。例:c
がコントラクトの場合、c.transfer(...)
からaddress(c).transfer(...)
に、c.balance
はaddress(c).balance
に代えてください。 - 関係ないコントラクト型同士の明示的な変換は現在できません。あるコントラクト型から、そのベースもしくは親(祖先)のコントラクト型への変換のみ可能です。もし継承していないのにも関わらず、あるコントラクトから別のコントラクトへの変換ができると考えているのであれば、まず
address
へ変換することで対処できます。例:A
とB
がコントラクト型でB
がA
を継承しておらず、b
がB
型のコントラクトである場合、A(address(b))
を使うことでb
をA
型に変換できます。
下記で説明されている通り、payable fallbackファンクションのマッチングに気をつける必要があります。
address
型はaddress
とaddress payable
に分けられ、address payable
だけがtransfer
ファンクションを使えます。address payable
は直接address
に変換可能ですが、逆はできません。address
からaddress payable
への変換はuint160
を通じて行うことで可能です。c
がコントラクトで、payable fallbackファンクションを持っていた場合、address(c)
はaddress payable
になります。もし withdraw pattern を使っているのであれば、コードを変える必要はきっとないでしょう。なぜなら、transfer
は 保存されているアドレスの代わりにmsg.sender
にのみ使われ、msg.sender
はaddress payable
だからです。- 異なるサイズの
bytesX
とuintY
間の変換は現在できません。なぜなら、bytesX
は右パディングでuintY
は左パディングのため、予期しない変換結果を生じる可能性があるためです。変換前に型内でサイズは調整される必要があります。例えば、始めにbytes4
をbytes8
に変換してからuint64
に変換することでbytes4
(4 bytes) をuint64
(8 bytes) に変換することができます。uint32
を通じて変換すると逆側のパディングをすることになります。 - payableではないファンクションで
msg.value
を使うこと(もしくはmodifierを使って実行する)はセキュリティの機能によりできません。
payable
のファンクションに変換するか、msg.value
を使う新しいinternalのファンクションを作ってください。
- 分かりやすくするため、標準入力がソースとして使用される場合、コマンドラインインターフェースに
-
が必要です。
Deprecated Elements¶
このセクションでは以前の機能やシンタックスで非推奨に変更になったリストを紹介します。これらの多くは既に v0.5.0
のexperimental modeでは使用できません。
Command Line and JSON Interfaces¶
- コマンドラインオプションの
--formal
(形式的検証のため以前はWhy3のアウトプットを生成していた)は非推奨隣、現在は削除されました。新しい形式的検証のモジュールのSMTCheckerはpragma experimental SMTChecker;
で使うことができます。 - 中間言語の名前が
Julia
からYul
へ変更になったためコマンドラインオプションの--julia
は--yul
へ名前が変更されました。 - コマンドラインオプションの
--clone-bin
と--combined-json clone-bin
は削除されました。 - 空の接頭辞での理マッピングはできません。
- JSON ASTフィールドの
constant
とpayable
は削除されました。その情報は現在stateMutability
フィールドにあります。 - JSON ASTフィールドの
FunctionDefinition
ノード
の isConstructor
は kind
というフィールドに置き換えられ、"constructor"
、"fallback"
、``"function"` という値を持つことができます。
- リンクしてないbinary hexファイルでは、ライブラリアドレスのプレースホルダは正規のライブラリの名前全体のkeccak256ハッシュの最初の16進数の36文字で、
$...$
に囲われています。
以前は正規のライブラリの名前だけ使われていました。これにより、特にパスが長い時は名前が衝突する可能性が減ります。バイナリファイルは現在、このプレースホルダから正規の名前全体のマッピングのリストを含んでいます。
Constructors¶
- コンストラクトは現在、
constructor
を使って定義しなければいけません。 - 括弧なしのベースのコンストラクトを呼び出すことは現在できません。
- 同じ継承の階層でベースのコンストラクトの引数をな何度も決めることは現在できません。
- 間違った数の引数でコンストラクタを呼ぶことはできません。もし引数を渡さないで継承の関係を指定したい場合は、括弧をつけないで下さい。
Functions¶
callcode
ファンクションは現在使えません()。
Function callcode
is now disallowed ( delegatecall
が使われます)。インラインアセンブリを使えば使うことができます。
suicide
は現在使えません(selfdestruct
が使われます).sha3
は現在使えません(keccak256
が使われます).throw
は現在使えません(revert
、require
、assert
が使われます)。
Conversions¶
- 明示的にも暗示的にも10進数リテラルの
bytesXX
型への変換は現在使えません。 - 明示的にも暗示的にも16進数リテラルの異なるサイズの
bytesXX
型への変換は現在使えません。
Literals and Suffixes¶
- 閏年で混乱してしまうため
years
という単位名は現在使えません。数字の後に - 数字の続かない末尾のドットは使えません。
- 16進数の数字とuintの単位名(例:
0x1e wei
) は一緒に使えません。 - 16進数に接頭辞
0X
は使えません。0x
だけ使えます。
Variables¶
- 分かりやすさのため、空の構造体の宣言は現在できません。
- 分かりやすさのため、現在
var
キーワードは使えません。 - タプルへの異なった要素数の割り当ては現在できません。
- コンパイル時に定数でない定数値は使えません。
- 値の数と変数の数が合わない複数変数の宣言は現在使えません。
- 初期化されていないストレージ変数は現在使えません。
- 空のタプル要素は現在使えません。
- 変数と構造体の循環参照の検知の繰り返しは256回までと制限されています。
- 長さが0の固定長さ配列は現在使えません。
Syntax¶
- ファンクションのstate mutability modifierとしての
constant
は現在使えません。 - 真偽式は算術演算を行えません。
- 単項の
+
演算子は使えません。 - リテラルは以前の明示的な型への変換なしでは
abi.encodePacked
を使えません。 - 返り値があるファンクションでの空のリターンは現在使えません。
- "loose assembly"シンタックスは現在完全に使えません。つまりjump label、jump、非ファンクショナルな指示はもう使えません。新しい
while
、switch
、if
を代わりに使ってください。 - 実行されないファンクションではmodifierは使えません。
- 名前のついた返り値のファンクション型は使えません。
- if/while/forのブロックでないボディ内での単文での変数の宣言はできません。
- New keywords:
calldata
andconstructor
. - New reserved keywords:
alias
,apply
,auto
,copyof
,define
,immutable
,implements
,macro
,mutable
,override
,partial
,promise
,reference
,sealed
,sizeof
,supports
,typedef
andunchecked
.
Interoperability With Older Contracts¶
インターフェースを定義すれば、Solidityバージョン0.5.0以前(もしくは以降)で書かれたコントラクトで使うことができます。 0.5.0以前のバージョンで作った以下のコントラクトを見てください。
// This will not compile with the current version of the compiler
pragma solidity ^0.4.25;
contract OldContract {
function someOldFunction(uint8 a) {
//...
}
function anotherOldFunction() constant returns (bool) {
//...
}
// ...
}
これは0.5.0ではコンパイルできませんが、互換性のあるインターフェースは定義できます。
pragma solidity ^0.5.0;
interface OldContract {
function someOldFunction(uint8 a) external;
function anotherOldFunction() external returns (bool);
}
オリジナルのコントラクトでは constant
と宣言されているのにも関わらず、anotherOldFunction
を view
で宣言していないことに注目して下さい。これはSolidity v0.5.0から staticcall
が view
ファンクションを呼ぶのに使われ始めたからです。
v0.5.0以前では constant
キーワードは強制ではありませんでしたので、constant
と宣言されたファンクションを staticcall
で呼び出してもrevertする可能性がありました。なぜなら、constant
ファンクションはストレージを修正しようとする可能性があったためです。
結論としては、古いコントラクト用のインターフェースを定義するときには、staticcall
で確実にファンクションが動作する様に、constant
の代わりに view
を使った方が良いでしょう。
上記で定義されたインターフェースがあれば、もう簡単に0.5.0以前のコントラクトをデプロイできます。
pragma solidity ^0.5.0;
interface OldContract {
function someOldFunction(uint8 a) external;
function anotherOldFunction() external returns (bool);
}
contract NewContract {
function doSomething(OldContract a) public returns (bool) {
a.someOldFunction(0x42);
return a.anotherOldFunction();
}
}
同様に、0.5.0以前のライブラリは実行せず、リンク中にそのライブラリのアドレスを渡さなくても、そのライブラリのファンクションを定義することで、使用することができます(リンキングにどうやってコマンドラインコンパイラを使うかは Using the Commandline Compiler を参照ください)。
pragma solidity ^0.5.0;
library OldLibrary {
function someFunction(uint8 a) public returns(bool);
}
contract NewContract {
function f(uint8 a) public returns (bool) {
return OldLibrary.someFunction(a);
}
}
Example¶
下記の例ではあるコントラクトとこのセクションで紹介したいくつかの変更を加えたSolidityバージョン0.5.0のコントラクトを紹介します。
Old version:
// This will not compile
pragma solidity ^0.4.25;
contract OtherContract {
uint x;
function f(uint y) external {
x = y;
}
function() payable external {}
}
contract Old {
OtherContract other;
uint myNumber;
// Function mutability not provided, not an error.
function someInteger() internal returns (uint) { return 2; }
// Function visibility not provided, not an error.
// Function mutability not provided, not an error.
function f(uint x) returns (bytes) {
// Var is fine in this version.
var z = someInteger();
x += z;
// Throw is fine in this version.
if (x > 100)
throw;
bytes b = new bytes(x);
y = -3 >> 1;
// y == -1 (wrong, should be -2)
do {
x += 1;
if (x > 10) continue;
// 'Continue' causes an infinite loop.
} while (x < 11);
// Call returns only a Bool.
bool success = address(other).call("f");
if (!success)
revert();
else {
// Local variables could be declared after their use.
int y;
}
return b;
}
// No need for an explicit data location for 'arr'
function g(uint[] arr, bytes8 x, OtherContract otherContract) public {
otherContract.transfer(1 ether);
// Since uint32 (4 bytes) is smaller than bytes8 (8 bytes),
// the first 4 bytes of x will be lost. This might lead to
// unexpected behavior since bytesX are right padded.
uint32 y = uint32(x);
myNumber += y + msg.value;
}
}
New version:
pragma solidity ^0.5.0;
contract OtherContract {
uint x;
function f(uint y) external {
x = y;
}
function() payable external {}
}
contract New {
OtherContract other;
uint myNumber;
// Function mutability must be specified.
function someInteger() internal pure returns (uint) { return 2; }
// Function visibility must be specified.
// Function mutability must be specified.
function f(uint x) public returns (bytes memory) {
// The type must now be explicitly given.
uint z = someInteger();
x += z;
// Throw is now disallowed.
require(x > 100);
int y = -3 >> 1;
// y == -2 (correct)
do {
x += 1;
if (x > 10) continue;
// 'Continue' jumps to the condition below.
} while (x < 11);
// Call returns (bool, bytes).
// Data location must be specified.
(bool success, bytes memory data) = address(other).call("f");
if (!success)
revert();
return data;
}
using address_make_payable for address;
// Data location for 'arr' must be specified
function g(uint[] memory arr, bytes8 x, OtherContract otherContract, address unknownContract) public payable {
// 'otherContract.transfer' is not provided.
// Since the code of 'OtherContract' is known and has the fallback
// function, address(otherContract) has type 'address payable'.
address(otherContract).transfer(1 ether);
// 'unknownContract.transfer' is not provided.
// 'address(unknownContract).transfer' is not provided
// since 'address(unknownContract)' is not 'address payable'.
// If the function takes an 'address' which you want to send
// funds to, you can convert it to 'address payable' via 'uint160'.
// Note: This is not recommended and the explicit type
// 'address payable' should be used whenever possible.
// To increase clarity, we suggest the use of a library for
// the conversion (provided after the contract in this example).
address payable addr = unknownContract.make_payable();
require(addr.send(1 ether));
// Since uint32 (4 bytes) is smaller than bytes8 (8 bytes),
// the conversion is not allowed.
// We need to convert to a common size first:
bytes4 x4 = bytes4(x); // Padding happens on the right
uint32 y = uint32(x4); // Conversion is consistent
// 'msg.value' cannot be used in a 'non-payable' function.
// We need to make the function payable
myNumber += y + msg.value;
}
}
// We can define a library for explicitly converting ``address``
// to ``address payable`` as a workaround.
library address_make_payable {
function make_payable(address x) internal pure returns (address payable) {
return address(uint160(x));
}
}
Security Considerations¶
想定通りに動くソフトウェアを作るのは割と簡単である一方で、想定していない 方法で誰かがそのソフトウェアを使うのをチェックするのは非常に難しいです。
Solidityでは、トークンやもしかしたらもっと価値のあるものを扱うためにスマートコントラクトを使うので、これは非常に重要です。さらに、スマートコントラクトの全ての実行は公に行われ、加えてソースコードは多くの場合誰でも見ることができます。
もちろん常にどの程度危険があるのか考えなくてはいけません。 スマートコントラクトと公にオープンなWebサービス(悪意のあるものも)やオープンソースを比較して下さい。 もし食料品の買い物リストをwebサービスに保存するなら、さほど気にする必要はないと思いますが、銀行口座を管理するなら、もっと気にする必要があります。
このセクションではいくつかの隠れた危険や、一般的なセキュリティに関する勧告をリストアップしますが、もちろん完璧にはできません。また、もしコントラクトコードにバグがなかったとしても、コンパイラやプラットフォーム自体にバグがあるかもしれません。パブリックに知られているコンパイラのセキュリティ関連のバグは list of known bugs で確認でき、これはコンピュータでも読み込めます。Solidityコンパイラのコード生成プログラムもカバーするバグの報奨制度もあります。
オープンソースドキュメントでは毎度の事ながら、このセクションをより良くするのにご助力下さい(特に、用例は助かります)。
Pitfalls¶
Private Information and Randomness¶
スマートコントラクトで使うもの全てはパブリックに見ることができます。たとえそれがローカル変数であったり、private
とついた状態変数だったとしてもです。
スマートコントラクトでマイナーにズルをさせない様に乱数を使うのは結構難しいです。
Re-Entrancy¶
あるコントラクト(A)から別のコントラクト(B)に対する相互作用とEtherの送金はコントラクト(B)にコントロールを渡してしまいます。このおかげで、相互作用が完了する前にBがAに戻れてしまいます。下記の例ではバグが入っています(コントラクト全体ではなく一部です)。
pragma solidity >=0.4.0 <0.6.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
/// Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() public {
if (msg.sender.send(shares[msg.sender]))
shares[msg.sender] = 0;
}
}
send
の一部としてガスが制限されているので大きな問題にはなりませんが、脆弱性を晒しています。Etherの送金は常にコードの実行を含んでいますので、受療者は withdraw
をコールバックするコントラクトにもなり得ます。これは何度もリファンドすることを可能にし、基本的にはコントラクト中の全てのEtherを引き出せます。特に、下記のコントラクトでは、デフォルトで残っているガスを全て転送する call
が使われているので、攻撃者が複数回リファンドができます。
pragma solidity >=0.4.0 <0.6.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
/// Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() public {
(bool success,) = msg.sender.call.value(shares[msg.sender])("");
if (success)
shares[msg.sender] = 0;
}
}
re-entrancyを避けるために、下記で説明する様にChecks-Effects-Interactionsパターンを使うことができます。
pragma solidity >=0.4.11 <0.6.0;
contract Fund {
/// Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() public {
uint share = shares[msg.sender];
shares[msg.sender] = 0;
msg.sender.transfer(share);
}
}
re-entrancyはEtherの送金だけでなく、他のコントラクト上でのファンクションコールでも起こり得ることを覚えておいて下さい。さらに、マルチコントラクトも考慮に入れる必要があります。呼び出されたコントラクトはあなたが使っている他のコントラクトのステートを変更する可能性があります。
Gas Limit and Loops¶
例えば、ストレージの値によって決まるループの様な繰り返しの回数が決まっていないループを使うときには気をつける必要があります。
ブロックガスリミットのため、トランザクションはある量のガスしか消費できません。明示化している、もしくは通常の演算のため、ループの繰り返し回数はブロックガスリミットを超えてしまい、あるところで不備がないコントラクトでも止まってしまいます。
これはブロックチェーンからデータを読み込むだけの view
には適用されないかもしれませんが、オンチェーンの演算の一部として他のコントラクトから呼ばれた場合、その演算を止めるかもしれません。あなたのコントラクトのドキュメンテーションにこの様なケースについて明記して下さい。
Sending and Receiving Ether¶
- 現状誰かがコントラクトもしくは"external accounts"にEtherを送るのを止めることはできません。
コントラクトは通常のtransferに対しては反応したり、拒否したりできますが、メッセージコールを生成しないでEtherを移動させる方法があります。その1つはシンプルにコントラクトアドレスに"マイニング"する方法で、2つ目は
selfdestruct(x)
を使うことです。 - コントラクトがEtherを受け取ったとき(ファンクションを呼び出さないで)、フォールバックファンクションが実行されます。 もしフォールバックファンクションがなかった場合、Etherの送金は拒否されます(例外が投げられます)。 フォールバックファンクションの実行中、コントラクトはその時渡された利用可能な"gas stipend"にのみ頼っています(2300ガス)。このstipendはストレージを変更するには足りません(これが普通だとは考えないで下さい。このstipendは将来のハードフォークで変わる可能性があります)。 この方法でコントラクトが確実にEtherを受け取れる様にするために、フォールバックファンクションのガス要求を確認して下さい(具体的にはRemixの"details"セクションで)。
addr.call.value(x)("")
を使ってコントラクトにもっとガスを送る方法があります。 これは本質的にはaddr.transfer(x)
と同じで、それは残っている全てのガスを送り、受領者がもっとガスがかかる演算を行わすことができます(自動でエラーを出す代わりにフェイラーコードを返します)。これは送信したコントラクトへのコールバックや多分想定していなかった他のステートの変更を含みます。そのため、健全なユーザに大きな柔軟性を与えますが、同時に悪意のあるユーザにも与えてしまいます。- もし
address.transfer
を使ってEtherを送りたい場合、気を付けないといけないことがあります。- もし受領者がコントラクトの場合、そのフォールバックファンクションが実行され、そのファンクションは次々にそのコントラクトにコールバックができます。
- コール深さが1024以上になるとEtherの送信は失敗する可能性があります。呼び出し元はコール深さの完全なコントロールを握っていますので、transferを失敗させることができます。この可能性を考慮に入れるか、
send
を使ってその返り値を常にチェックして下さい。できたら、受領者がEtherを代わりに引き出せるパターンをコントラクトを書くときに使って下さい。 - 割り当てられたガス以上のガスを受領コントラクトの実行で要求した場合もEtherの送信は失敗します(明示的に
require
、assert
、revert
、throw
を明示的に使った場合、もしくは処理コストが高すぎるため)- "ガス不足"(OOG)になります。返り値チェックと、transfer
もしくはsend
を使う場合、コントラクトの送信中に処理を止める方法を受領者に与えてしまうかもしれません。もう一度、ここでのベストプラクティスは "send" パターンの代わりに"withdraw" パターンを使うこと です。
Callstack Depth¶
1024の最大コールスタックを超える場合、externalのファンクションコールはどんな時も失敗します。この様な状況では、Solidityは例外を投げます。 悪意のあるユーザはあなたのコントラクトと繋がる前にコールスタックに大きい値を入れれるかもしれません。
コールスタックが使い尽くされた場合、.send()
は例外を 投げません が、false
を返すということを覚えておいて下さい。低レベルファンクションの``.call()``、.callcode()
、.delegatecall()
、.staticcall()
も同じ様な挙動をします。
tx.origin¶
承認するのにtx.originは使わないでください。例えば、あなたが下記の様なウォレットコントラクトを持っていたとします。
pragma solidity ^0.5.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
address owner;
constructor() public {
owner = msg.sender;
}
function transferTo(address payable dest, uint amount) public {
require(tx.origin == owner);
dest.transfer(amount);
}
}
今、誰かが下記の攻撃用ウォレットのアドレスにEtherを騙して送らさせます。
pragma solidity ^0.5.0;
interface TxUserWallet {
function transferTo(address payable dest, uint amount) external;
}
contract TxAttackWallet {
address payable owner;
constructor() public {
owner = msg.sender;
}
function() external {
TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
}
}
もしあなたのウォレットが承認に msg.sender
をチェックした場合、オーナーアドレスではなく、攻撃用ウォレットのアドレスを手に入れるでしょう。しかし、tx.origin
をチェックした場合、トランザクションを始めたオリジナルのアドレスを取得しますので、それがオーナーアドレスになってしまいます。そして攻撃用ウォレットはすぐにファンドを全て取り出してしまいます。
Two's Complement / Underflows / Overflows¶
多数のプログラミング言語の様にSolidityの整数型は実際には整数ではありません。値が小さい時は整数に見えますが、値が大きくなると異なる挙動を示します。
例えば、次の式は真です: uint8(255) + uint8(1) == 0
これは overflow と言います。固定サイズの変数にその変数のデータ型の範囲外の数字(データ)を入れようとする演算が実行される時に起こります。underflow は逆の状況です: uint8(0) - uint8(1) == 255
。
一般に、2の補数表現の限度付近では符号付の数字のもっと特別なエッジケースがあります。
入力を合理的な範囲にサイズを制限するために require
を使ってみて下さい。また、潜在的なオーバーフローを検知するために、SMT checker を、または全てのオーバーフローに対してrevertさせたい時は SafeMath を使って下さい。
require((balanceOf[_to] + _value) >= balanceOf[_to])
の様なコードは値が期待通りかチェックするのにも役立ちます。
Minor Details¶
- 32バイトをフルで占有しない型は"汚れた上位ビット"を含んでいるかもしれません。これは
msg.data
にアクセスする場合、特に重要で、展性リスクがあります: あるファンクションf(uint8 x)
を生のバイト引数0xff000001
と0x00000001
で呼び出すトランザクションを作ることができます。両方ともコントラクトに渡され、x
に関する限り、両方とも数字の1
に見えますが、msg.data
は違います。そのため、もしkeccak256(msg.data)
を使うと、異なる結果が得られます。
Recommendations¶
Take Warnings Seriously¶
もしコンパイラが何か警告を発したら、そこは変えたほうが良いです。 たとえ特別セキュリティに関係ないと思っても、その裏側では別の問題があるかもしれません。 コンパイラが発する警告は全て少しのコードの変更で消すことができます。
最近実装された全ての警告が出る様に、常に最新バージョンのコンパイラを使って下さい。
Restrict the Amount of Ether¶
スマートコントラクト内に保存されるEther(もしくは他のトークン)の量を制限してください。もしあなたのソースコード、コンパイラ、もしくはプラットフォームにバグがあったら、このファンドは全て失われるかもしれません。もし、ロスを制限したいのであれば、Etherの量を制限して下さい。
Keep it Small and Modular¶
コントラクトは小さくそして簡単に理解できる様にしておいて下さい。他のコントラクトもしくはライブラリの関係ない機能を見つけて下さい。もちろんソースコードのクオリティに関する一般的な勧告も使います: ローカル変数の量やファンクションの長さなどを制限して下さい。ファンクションに注記をつけて下さい。そうすれば他の人はあなたの意図が分かりますし、コードと意図が同じかどうかも分かります。
Use the Checks-Effects-Interactions Pattern¶
ほとんどのファンクションはまずいくつかのチェックで始まります(誰がファンクションを呼び出したのか、引数は範囲に入っているか、十分なEtherを送っているか、その人はトークンを持っているかなど)。これらのチェックは最初に終わらせなければいけません。
2つ目のステップとして、全てのチェックが通ったら、現在のコントラクトの状態変数の処理を行うはずです。 どんなファンクションでも他のファンクションとの相互作用は最後の最後に行われるはずです。
昔のコントラクトはいくつかの処理をデプロイしてからexternalのファンクションコールがノンエラーステートを返すのを待っていました。これは上記で説明したre-entrancy問題のため、しばしば大きな失敗となります。
さらに、既知のコントラクトをコールしても未知のコントラクトをどんどんコールする可能性があるということに気をつけて下さい。そのため、おそらく常にこのパターンを適用するのが良いでしょう。
Include a Fail-Safe Mode¶
システムを完全に非中央集権的にすると全ての仲介を除去することになる一方で、特に新しいコードにある種のフェイルセーフメカニズムを含めておくことは良いアイデアかもしれません。
"Etherのリークがあるか?"、"トークンの合計がコントラクトのバランスと一致するか"などの様なセルフチェックを実行するファンクションをスマートコントラクトに追加することができます。 大量のガスはこのために使えないことは頭に入れておいて下さい。おそらく、オフチェーンの計算が必要になるかもしれません。
セルフチェックが失敗したら、コントラクトは自動的にある種の"フェイルセーフ"モードに変わります。例えば、ほとんどの機能を無効にしたり、コントロールを決められたそして信頼できるサードパーティに渡したり、単にシンプルな"give me back my money"コントラクトに変換したりします。
Ask for Peer Review¶
たくさんの人がコードを調べれば、たくさんの問題が見つかります。 人に自分のコードのレビューを頼むことでそのコードが読みやすいかどうかクロスチェックすることになります。その読みやすさは良いスマートコントラクトの大切なクライテリアです。
Formal Verification¶
形式的検証を使って、あなたのコードがある形式の仕様を満たしているか証明する自動計算を行うことができます。 仕様は基準に則っています(ソースコードの様に)が、通常よりシンプルです
形式的検証自体はあなたがやった事(仕様)とどの様にやった(実際の実行)かの違いを理解するのに役立つだけです。そのため、仕様があなたのやりたかった事であるか、意図しない処理を見逃してないかどうかチェックする必要があります。
Resources¶
General¶
Solidity Integrations¶
Generic:
- EthFiddle
- ブラウザのSolidity IDE Solidityコードを書いて共有しましょう。サーバーサイドコンポーネントを使用します。
- Remix
- サーバーサイドコンポーネントのない統合コンパイラとSolidityランタイム環境を持つブラウザベースのIDEです。
- Solium
- Solidityのスタイルとセキュリティの問題を特定し修正するリンターです。
- Solhint
- セキュリティ、スタイルガイド、およびスマートコントラクトの検証のためのベストプラクティスルールを提供するSolidityリンターです。
- Superblocks Lab
- ブラウザベースのIDEです。ビルドインのブラウザベースのVMとMetamaskのインテグレーションです(Testnet / Mainnetへのワンクリックデプロイメント)。
Atom:
- Etheratom
- シンタックスハイライト、コンパイル、およびランタイム環境(バックエンドノードとVM互換)を特徴とするAtomエディター用のプラグインです。
- Atom Solidity Linter
- Solidityのリントを提供するAtomエディター用のプラグインです。
- Atom Solium Linter
- Soliumをベースとして使用するAtom用の構成可能なSolidtyリンターです。
Eclipse:
- YAKINDU Solidity Tools
- EclipseベースのIDEです。状況依存のコード補完とヘルプ、コードナビゲーション、構文の色分け、組み込みコンパイラ、クイックフィックス、およびテンプレートを備えています。
Emacs:
- Emacs Solidity
- シンタックスハイライトとコンパイルエラーレポートを提供するEmacsエディタ用のプラグインです。
IntelliJ:
- IntelliJ IDEA plugin
- IntelliJ IDEA(と他のすべてのJetBrains IDE)用のSolidityプラグインです。
Sublime:
- Package for SublimeText - Solidity language syntax
- SublimeTextエディタ用のSolidityシンタックスハイライトです。
Vim:
- Vim Solidity
- Vimエディタ用のSolidityシンタックスハイライトを提供するプラグインです。
- Vim Syntastic
- Vimエディタ用のコンパイルチェックを提供するプラグインです。
Visual Studio Code:
- Visual Studio Code extension
- Microsoft Visual Studio Code用のSolidityシンタックスハイライトとコンパイラを含むプラグインです。
Discontinued:
- Mix IDE
- 堅牢なスマートコントラクト設計、デバッグ、テストするためのQtベースのIDEです。
- Ethereum Studio
- 完全なEthereum環境へのシェルアクセスも提供する専用のWeb IDEです。
- Visual Studio Extension
- Microsoft Visual Studio用のSolidityコンパイラを含むプラグインです。
Solidity Tools¶
- Dapp
- Solidity用のビルドツール、パッケージマネージャ、そしてデプロイアシスタントです。
- Solidity REPL
- コマンドラインコンソールを使ってSolidityをすぐに試してみてください。
- solgraph
- Solidityの制御フローを視覚化し、潜在的なセキュリティの脆弱性を表示します。
- Doxity
- Solidityのためのドキュメントジェネレータです。
- evmdis
- バイトコードの静的解析を実行し、生のEVM操作よりも高いレベルの抽象化を提供するEVM逆アセンブラです。
- ABI to solidity interface converter
- スマートコントラクトのABIからコントラクトインターフェースを生成するためのスクリプトです。
- Securify
- スマートコントラクト用の完全に自動化されたオンライン静的アナライザです。脆弱性パターンに基づいたセキュリティーレポートを提供します。
- Sūrya
- スマートコントラクトのためのユーティリティツールです。コントラクトの構造に関する多数の視覚的な出力と情報を提供します。また、関数コールグラフのクエリもサポートしています。
- EVM Lab
- EVMとやり取りを行うツールパッケージです。VM、Etherchain API、およびガスコストの表示を行うトレースビューアが含まれています。
注釈
変数名、コメント、ソースコードのフォーマットなどの情報はコンパイルプロセスで失われ、元のソースコードを完全に回復することは不可能です。 元のソースコードを表示するためにスマートコントラクトを逆コンパイルすることは可能ではないかもしれません。しかし、その結果は有用なものでしょう。
Third-Party Solidity Parsers and Grammars¶
- solidity-parser
- JavaScript用のSolidityパーサです。
- Solidity Grammar for ANTLR 4
- ANTLR 4パーサジェネレータ用のSolidityグラマーです。
Using the compiler¶
Using the Commandline Compiler¶
注釈
この章の内容は、コマンドラインモードも含めて solcjs には使用することができません。
Solidityレポジトリのビルドターゲットの1つはsolidityコマンドラインコンパイラである solc
です。
solc --help
を使ってオプションを表示できます。このコンパイラは、単純なバイナリから抽象構文木(構文解析ツリー)へのアセンブリからガス代の見積もりまで、さまざまな出力を生成することができます。
単一ファイルのみをコンパイルしたい場合、該当のファイルを solc --bin sourceFile.sol
として実行すると、バイナリが出力されます。
solc
のより高度なアウトプットを取得したい場合は、 solc -o outputDirectory --bin --ast --asm sourceFile
を使用してすべてを別々のファイルに出力するように指定したほうがよいでしょう。
また、コントラクトをデプロイする前に、 solc --optimize --bin sourceFile.sol
を使用してコンパイルするときにオプティマイザを有効にしてください。
デフォルトでは、オプティマイザは、コントラクトがそのライフタイムにわたって200回呼び出されると仮定して、コントラクトを最適化します。
イニシャルコントラクトのデプロイのガス代をより安く、その後の関数実行時のガス代をより高くしたい場合は、--optimize-runs=1
と設定してください。
多くのトランザクションを想定していて、デフォルトより高いデプロイコストとアウトプットサイズを気にしないのであれば、 --optimize-runs
を大きな数に設定してください。
コマンドラインコンパイラはファイルシステムからインポートされたファイルを自動的に読み込みますが、以下のように prefix=path
を使ってリダイレクトするパスを明示することも可能です:
solc github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ file.sol
これはコンパイラに /usr/local/lib/dapp-bin
下の github.com/ethereum/dapp-bin/
で始まるものを検索するように指示します。
solc
は再マッピングターゲットの外側にあるファイルシステムからファイルを読みません。ただし、 import "/etc/passwd";
のように明示的に指定したディレクトリ外の再マッピングターゲットファイルが存在するときは /=/
を追加すれば読み込むことができるようになります。
また、空の再マッピングプレフィックスは許可されていません。
再マッピングを行う対象が複数個ある場合は、最も長い共通プレフィックスを持つものが選択されます。
セキュリティ上の理由から、コンパイラがアクセスできるディレクトリには制限があります。
コマンドラインで指定されたソースファイルのパス(そのサブディレクトリを含む)と再マッピングで定義されたパスはimport文で使用できますが、それ以外はすべてリジェクトされます。追加のパス(そのサブディレクトリを含む)は --allow-paths /sample/path, /another/sample/path
を使用することで許可することができます。
コントラクト内に libraries を使用している場合、バイトコードが __$53aea86b7d70b31448b230b20ae141a537$__
形式の部分文字列を含んでいることに気付くはずです。これらは実際のライブラリアドレスのプレースホルダになります。
このプレースホルダは、完全修飾ライブラリ名のkeccak256ハッシュをhexエンコーディングした34文字のプレフィックスです。
また、バイトコードファイルの最後には、 // <プレースホルダ> -> <完全修飾ライブラリ名>
という形式の行も含まれます。
そして、プレースホルダがどのライブラリを表すかを識別するための完全修飾ライブラリ名ソースファイルのパスとライブラリ名を :
で区切って指定します。
さらにリンカ(linker)として solc
を使うことができます。これはリンカがある場所にライブラリアドレスを挿入することを意味します:
各ライブラリのアドレスを指定するか、ファイルごとに文字列(1行ごとに1つライブラリを指定してください)を格納するには、 solc
で --libraries fileName
オプションを使って --libraries "file.sol:Math:0x1234567890123456789012345678901234567890 file.sol:Heap:0xabCD567890123456789012345678901234567890"
とコマンドに追加します。
オプション --link
を付けて solc
を呼び出すと、すべての入力ファイルは上記の __$53aea86b7d70b31448b230b20ae141a537$__
形式のリンクされていないバイナリ(hexエンコーディング)として解釈され、インプレースリンクとなります(入力が標準入力から読み取られる場合は、標準出力に書き込まれます)。この場合、 --libraries
以外のすべてのオプションは無視されます( -o
を含みます)。
solc
が --standard-json
オプション付きで呼び出された場合、(以下で説明するように)標準入力にJSONでの入力が求められ、標準出力にJSON出力が返されます。これは、より複雑で特に自動化された用途の場合に推奨されるインターフェースです。
注釈
ライブラリのプレースホルダは、以前はライブラリのハッシュではなく、ライブラリ自体の完全修飾名でした。
このフォーマットはまだ solc --link
によってサポートされてはいますが、コンパイラはもうその出力に対応していません。
この変更は、完全修飾ライブラリ名の最初の36文字しか使用できないことから、ライブラリ間でのコンフリクトが発生する可能性を減らすために採用されました。
Setting the EVM version to target¶
コントラクトコードをコンパイルする際、特定の機能や動作を回避するために、コンパイルするEthereum virtual machine(EVM)のバージョンを指定できます。
警告
誤ったEVMバージョンでコンパイルすると、誤った、奇妙な、そして失敗するような振る舞いをするかもしれません。 特にプライベートチェーンを実行している場合は、必ず一致するEVMバージョンを使用してください。
コマンドラインでは、次のようにEVMのバージョンを指定できます:
solc --evm-version <VERSION> contract.sol
standard JSON interface 内では、 "settings"
フィールド内の "evmVersion"
キーを使用してください:
{
"sources": { ... },
"settings": {
"optimizer": { ... },
"evmVersion": "<VERSION>"
}
}
Target options¶
以下は、ターゲットEVMのバージョンと、各バージョンで導入されたコンパイラ関連の変更のリストです。 各バージョン間の下位互換性は保証されていません。
homestead
(1番古いバージョン)tangerineWhistle
- 他のアカウントにアクセスするためのガス代が増加します。これはガス見積もりとオプティマイザに関連することにより起こります。
- すべてのガスは、デフォルトでは外部コールとして送信されていましたが、以前は一定量を保持する必要がありました。
spuriousDragon
exp
オペコードのガスコストが増加します。これはガス推定とオプティマイザに関連することにより起こります。
byzantium
(default)- オペコード
returndatacopy
、returndatasize
、staticcall
はアセンブリで利用可能です。 staticcall
オペコードは、ライブラリ以外のviewやpureの修飾子が付いた関数を呼び出すときに使われます。これはEVMレベルで関数が状態を変更するのを防ぎます。つまり、無効な型変換を使ったときにも適用されます。- 関数呼び出しから返された動的データにアクセスすることが可能です。
revert
オペコードが導入されました。revert()
がガスを無駄にしません。
- オペコード
constantinople
(開発中)- オペコード
shl
、shr
およびsar
はアセンブリで利用できます。 - シフト演算子はシフト演算コードを使用するため、必要なガスが少なくて済みます。
- オペコード
Compiler Input and Output JSON Description¶
Solidityコンパイラとインターフェースをとるための推奨される方法は、いわゆるJSON入出力インターフェースです。 これは特に複雑で自動化されたセットアップの際に有用です。 コンパイラのすべてのディストリビューションで同じインタフェースが提供されています。
そのフィールドは度々変更されることがあり、いくつかはオプションです(このドキュメントに書かれてある通りです)。 ただし、私達は後方互換性のある変更だけをします。
コンパイラAPIはJSON形式の入力を要求し、コンパイル結果をJSON形式の出力に出力します。
以下のサブセクションでは、例を通してその形式を説明します。 下にあるようなコメントはもちろん使うことはできません。ここでは説明の目的で使用されているだけです。
Input Description¶
{
// Required: ソースコードの言語。"Solidity"、 "Vyper"、 "lll"、 "assembly"など
language: "Solidity",
// Required
sources:
{
// キーはソースファイルのグローバルネームです。
// インポートは再マッピングを通して他のファイルを使うことができます(下記を参照してください)
"myFile.sol":
{
// Optional: ソースファイルのkeccak256ハッシュ
// URL経由でインポートした場合、取得したコンテンツを検証するために使用されます。
"keccak256": "0x123...",
// Required("content"が使用されていない限りRequiredです。下記を参照してください): ソースファイルのURLs
// URLはこの順番でインポートされ、結果はkeccak256ハッシュ(利用可能な場合)に対してチェックされます。
// ハッシュが一致しない場合、またはいずれのURLでも成功しなかった場合は、エラーが発生します。
"urls":
[
"bzzr://56ab...",
"ipfs://Qma...",
// If files are used, their directories should be added to the command line via
// `--allow-paths <path>`.
"file:///tmp/path/to/file.sol"
]
},
"mortal":
{
// Optional: ソースファイルのkeccak256ハッシュ
"keccak256": "0x234...",
// Required("urls"が使用されていない限りRequiredです): ソースファイルのリテラル
"content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
}
},
// Optional
settings:
{
// Optional: 再マッピングのソート済みリスト
remappings: [ ":g/dir" ],
// Optional: オプティマイザー設定
optimizer: {
// デフォルトではdisabledです
enabled: true,
// コードを実行する回数を最適化します。
// 値が小さいほどイニシャルデプロイコストが最適化され、値が高いと高頻度で使用するときにより最適化されます。
runs: 200
},
// コンパイルするEVMのバージョン。型チェックとコード生成に影響します。
// homestead、tangerineWhistle、spuriousDragon、byzantium、constantinopleのいずれかです。
evmVersion: "byzantium",
// メタデータ設定(optional)
metadata: {
// URLではなくリテラルのみを使用(デフォルトではfalseです)
useLiteralContent: true
},
// ライブラリのアドレスです。ここにすべてのライブラリが指定されていないと、出力データがライブラリにリンクされていない異なるオブジェクトになる可能性があります。
libraries: {
// 最上位のキーは、ライブラリが使用されているソースファイルの名前です。
// 再マッピングが使用されている場合、このソースファイルは再マッピングが適用された後のグローバルパスと一致する必要があります。
// このキーが空の文字列の場合、グローバルレベルを参照します。
"myFile.sol": {
"MyLib": "0x123123..."
}
}
// 以下は、ファイル名とコントラクト名に基づいて目的の出力を選択するために使用できます。
// このフィールドを省略すると、コンパイラは型チェックをロードして実行しますが、エラー以外の出力は生成しません。
// ファーストレベルのキーはファイル名、セカンドレベルのキーはコントラクト名です。
// 空のコントラクト名は、コントラクトではなくASTのようなソースファイル全体に関連付けられている出力に使用されます。
// コントラクト名としてのスター(*)は、ファイル内のすべての契約を表します。
// 同様に、ファイル名としてのスター(*)はすべてのファイルに一致します。
// コンパイラが生成する可能性のあるすべての出力を選択するには、"outputSelection:{" * ":{" * ":[" * "]、" ":[" * "]}}"を使用します。
// ただし、これはコンパイルプロセスを不必要に遅くする可能性があることに留意してください。
//
// 利用できるアウトプットの型は以下の通りです:
//
// ファイルレベル(コントラクト名として空文字列が必要):
// ast - 全ソースファイルのAST
// legacyAST - 全ソースファイルのlegacy AST
//
// コントラクトレベル(コントラクト名か"*"が必要です):
// abi - ABI
// devdoc - 開発者向けドキュメント(natspec)
// userdoc - ユーザー向けドキュメント(natspec)
// metadata - メタデータ
// ir - desugaringする前の新しいアセンブリフォーマット
// evm.assembly - desugaringした後の新しいアセンブリフォーマット
// evm.legacyAssembly - JSONでの古いスタイルのアセンブリ
// evm.bytecode.object - バイトコードオブジェクト
// evm.bytecode.opcodes - オペコードのリスト
// evm.bytecode.sourceMap - ソースマッピング(デバッグに役立ちます)
// evm.bytecode.linkReferences - リンクリファレンス(もしunlinked objectであれば)
// evm.deployedBytecode* - デプロイ時のバイトコード(evm.bytecodeと同じオプションがあります)
// evm.methodIdentifiers - 関数ハッシュのリスト
// evm.gasEstimates - 関数のガス代の推定
// ewasm.wast - eWASM S-expressionsフォーマット(atmをサポートしていません)
// ewasm.wasm - eWASM binaryフォーマット(atmをサポートしていません)
//
// `evm` 、` evm.bytecode` 、 `ewasm` などを使用すると、その出力のすべてのターゲット部分が選択されます。
// さらに、 `*` はすべてを要求するためのワイルドカードとして使うことができます。
//
outputSelection: {
// 各コントラクトのメタデータとバイトコードの出力を有効にします。
"*": {
"*": [ "metadata", "evm.bytecode" ]
},
// defに定義されているMyContractのabiおよびオペコード出力を有効にします。
"def": {
"MyContract": [ "abi", "evm.bytecode.opcodes" ]
},
// 各コントラクトのソースマップ出力を有効にします。
"*": {
"*": [ "evm.bytecode.sourceMap" ]
},
// すべての単一ファイルのlegacy AST出力を有効にします。
"*": {
"": [ "legacyAST" ]
}
}
}
}
Output Description¶
{
// Optional: エラー/警告が発生しなかった場合は存在しません
errors: [
{
// Optional: ソースファイル内の位置
sourceLocation: {
file: "sourceFile.sol",
start: 0,
end: 100
],
// Mandatory: エラータイプ。"TypeError"、"InternalCompilerError"、"Exception"など。
// すべての型のリストは以下を参照してください。
type: "TypeError",
// Mandatory: エラーが発生したコンポーネント。"general"、"ewasm"など。
component: "general",
// Mandatory ("error"もしくは"warning")
severity: "error",
// Mandatory
message: "Invalid keyword"
// Optional: ソースの場所でフォーマットされたメッセージ
formattedMessage: "sourceFile.sol:100: Invalid keyword"
}
],
// これはファイルレベルの出力を含むものです。outputSelectionの設定によって制限/フィルタリングできます。
sources: {
"sourceFile.sol": {
// ソースの識別子(ソースマップで使用)
id: 1,
// ASTオブジェクト
ast: {},
// legacy ASTオブジェクト
legacyAST: {}
}
},
// これはコントラクトレベルの出力を含むものです。outputSelectionの設定によって制限/フィルタリングできます。
contracts: {
"sourceFile.sol": {
// 使用されている言語にコントラクト名がない場合、このフィールドは空の文字列と等しくなります。
"ContractName": {
// Ethereum Contract ABIです。もし空なら、空配列として表されます。
// https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABIを参照してください。
abi: [],
// Metadata Outputのドキュメント(serialised JSON string)を参照してください。
metadata: "{...}",
// ユーザードキュメント(natspec)
userdoc: {},
// 開発者向けドキュメント(natspec)
devdoc: {},
// 中間表現(string)
ir: "",
// EVMに関連する出力
evm: {
// Assembly (string)
assembly: "",
// 過去の形式のアセンブリ(object)
legacyAssembly: {},
// バイトコードと関連する詳細
bytecode: {
// hex stringとしてのバイトコード
object: "00fe",
// オペコードのリスト(string)
opcodes: "",
// 文字列としてのソースマッピング。ソースマッピング定義を参照してください。
sourceMap: "",
// もし存在する場合、これはunlinked objectとなります。
linkReferences: {
"libraryFile.sol": {
// バイトコードへのバイトオフセット。リンクすると、その位置の20バイトが置き換えられます。
"Library1": [
{ start: 0, length: 20 },
{ start: 200, length: 20 }
]
}
}
},
// 上記と同じレイアウトです。
deployedBytecode: { },
// 関数ハッシュのリスト
methodIdentifiers: {
"delegate(address)": "5c19a95c"
},
// ガス代の推定を行う関数
gasEstimates: {
creation: {
codeDepositCost: "420000",
executionCost: "infinite",
totalCost: "infinite"
},
external: {
"delegate(address)": "25000"
},
internal: {
"heavyLifting()": "infinite"
}
}
},
// 出力に関連するeWASM
ewasm: {
// S-expressionsフォーマット
wast: "",
// バイナリ形式(hex string)
wasm: ""
}
}
}
}
}
Error types¶
JSONError
: JSON入力が必要なフォーマットに準拠していません。入力がJSONオブジェクトではない、言語がサポートされていない、など。IOError
: IOとimportプロセスエラーです。利用できないURLや提供されたソースのハッシュの不一致など。ParserError
: ソースコードが言語の規則に準拠していません。DocstringParsingError
: コメントブロック内のNatSpecタグは解析できません。SyntaxError
: シンタックスエラーです。continue
のような構文エラーはfor
ループの外側で使われます。DeclarationError
: 無効な、解決できない、またはコンフリクトする識別子名です。例えばIdentifier not found
など。TypeError
: 型システム内のエラーです。invalid type conversions や invalid assignmentsなど。UnimplementedFeatureError
: この機能はコンパイラではサポートされていませんが、将来のバージョンでサポートされる予定です。InternalCompilerError
: 内部バグがコンパイラで引き起こされました。 -これは問題として報告されるべきです。Exception
: コンパイル中にUnknown failureが発生しました。 - これは問題として報告されるべきです。CompilerError
: コンパイラスタックの無効な使用が起こりました。 - これは問題として報告されるべきです。FatalError
: Fatal errorにより正しく処理されませんでした。 - これは問題として報告されるべきです。Warning
: Warningです。コンパイルは止めませんでしたが、可能であれば対処するべきです。
Contract Metadata¶
SolidityコンパイラはJSONファイル形式でコントラクトのメタデータを自動生成します。 このメタデータは現在のコントラクト情報を含みます。そして、このJSONファイルを使って、コンパイラバージョンやソースコード、ABIやNatSpec(Ethereum Natural Language Specification Format)のドキュメントなど、ソースコードを検証しコントラクトをより安全に利用するための情報を知ることができます。
EVMコンパイラはこのメタデータのSwarmハッシュを各コントラクトにおけるバイトコードの末尾に追加します。 これにより、中央集権的なデータプロバイダに頼ることなくファイルを認証された方法で取得することができます。
また、他人がメタデータファイルにアクセスするためにはSwarm(または類似サービス)上にそのファイルをアップロードしなければなりません。
このとき ContractName_meta.json
と呼ばれるファイルを生成するには solc --metadata
コマンドを使います。
このファイルにはソースコードのSwarmリファレンスが含まれています。そのため、すべてのソースファイルとメタデータファイルをアップロードする必要があります。
メタデータは次のようなフォーマットです。この下の例は人間が読みやすいように書かれています。 適切にフォーマット化されたメタデータは、引用符を正しく使い、空白を最小限におさえ、すべてのオブジェクトのキーを個々のフォーマットに当てはめるように使うべきです。 また、ここではコメントは何かしらの説明をする目的以外に使うことはできません。
{
// Required: メタデータフォーマットのバージョン。
version: "1",
// Required: ソースコード言語。基本的には特定のサブバージョンを指定します。
language: "Solidity",
// Required: コンパイラについての詳細。内容は使用する言語に依存します。
compiler: {
// Required for Solidity: コンパイラのバージョン。
version: "0.4.6+commit.2dabbdf0.Emscripten.clang",
// Optional: 出力を生成するコンパイラバイナリのハッシュ。
keccak256: "0x123..."
},
// Required: コンパイルソースファイル/ユニット。ここではキーはファイルの名前を指します。
sources:
{
"myFile.sol": {
// Required: ソースファイルのkeccak256ハッシュ
"keccak256": "0x123...",
// Required ("content"を使用していない場合):
// ソートされたソースファイルに対応するURL。
// プロトコルは多少なりとも任意ですが、Swarm URLをおすすめします。
"urls": [ "bzzr://56ab..." ]
},
"mortal": {
// Required: ソースファイルのkeccak256ハッシュ
"keccak256": "0x234...",
// Required ("url"を使用していない場合): 正確なソースファイルの内容
"content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
}
},
// Required: コンパイラ設定
settings:
{
// Required for Solidity: remappingsのソート済みリスト
remappings: [ ":g/dir" ],
// Optional: オプティマイザー設定 (デフォルト値はfalse)
optimizer: {
enabled: true,
runs: 500
},
// Required for Solidity: コントラクト名とファイル、またはこのメタデータが作成されるライブラリ名。
compilationTarget: {
"myFile.sol": "MyContract"
},
// Required for Solidity: 使われているライブラリのアドレス
libraries: {
"MyLib": "0x123123..."
}
},
// Required: コントラクト生成に関する情報
output:
{
// Required: コントラクトのABI定義
abi: [ ... ],
// Required: コントラクトのNatSpecユーザードキュメント
userdoc: [ ... ],
// Required: コントラクトのNatSpec開発者ドキュメント
devdoc: [ ... ],
}
}
警告
結果として生じるコントラクトのバイトコードにはメタデータのハッシュが含まれているため、メタデータのどんな些細な変更であってもバイトコードが変更されることになります。バイトコードはファイル名やパスに対する変更も含んでおり、また、メタデータはすべてのソースファイルのハッシュを含むため、たとえホワイトスペースを1つ付け加えるなどの変更においても結果として別々のメタデータとバイトコードが生成されることになります。
注釈
上記のABIの定義には決まった順序はありません。コンパイラのバージョンによって変わる可能性があります。
Encoding of the Metadata Hash in the Bytecode¶
将来的にメタデータファイルを取得する他の方法がサポートされる可能性があるため、 {"bzzr0": <Swarm hash>}
のマッピングは CBOR でエンコードされ保存されます。このエンコーディングの始点を見つけることは簡単ではありません。そのため、そのエンコーディング長は2バイトのビッグエンディアンで追加されます。Solidityコンパイラの現在のバージョンでは、デプロイされたバイトコードの末尾まで以下のように追加します:
0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29
このデータを取得するために、デプロイされたバイトコードの末尾をチェックすることができます。 そのパターンに一致させ、Swarmハッシュを使用してファイルを取得します。
注釈
Solidityコンパイラは現在メタデータの"swarm version 0"ハッシュを使用しています。
しかし、将来的にこれは変更されるため 0xa1 0x65 'b' 'z' 'z' 'r' '0'
から始まるシーケンスを完全には信用しないでください。私たちは他にも
このCBORストラクチャに他のデータを追加するでしょう。そのため、ベストな選択肢は適切なCBORパーサーを使用することです。
Usage for Automatic Interface Generation and NatSpec¶
次の方法でメタデータは使用されます: コントラクトと関わるコンポーネント(e.g. Mistやその他のウォレット)がコントラクトコードを取得します。そして、Swarmハッシュを取得します。ファイルは上記のようにJSON形式でデコードされています。 その後、コンポーネントはABIをもとにコントラクトにおける基本的なユーザーインターフェースを自動生成します。
さらに、ウォレットはNatSpecユーザードキュメントを利用して、ユーザーがコントラクトと対話するときはいつでもトランザクション署名の許可を要求するとともに、確認メッセージをユーザーに表示することができます。
Ethereum Natural Specification (NatSpec)に関する詳しい情報は ここ で参照することができます。
Usage for Source Code Verification¶
コンパイル検証のために、メタデータファイル内のリンクを通じてSwarmからソースを取得することができます。正しいバージョンのコンパイラ("official"なコンパイラとして確認されているもの)が指定の設定のもと呼び出されます。そして、結果として得られるバイトコードはトランザクション生成のデータや CREATE
オペコードのデータなどと比べられます。ここでハッシュはバイトコードの一部であるため、自動的にメタデータを検証します。
残りのデータはコンストラクタにおける入力データに相当します。これはインタフェースに従ってデコードされ、ユーザに提示されるべきです。
Contract ABI Specification¶
Basic Design¶
コントラクトにおけるアプリケーションバイナリインターフェース(Application Binary Interface, ABI)はイーサリアムエコシステム内でコントラクトに接続するスタンダードなインターフェースです。コントラクトもABIもブロックチェーン外で作成され、ABIはコントラクトとコントラクトを接続するために使用されます。このドキュメントで紹介されているように、データはABIに従う形でエンコーディングされます。このエンコーディングのやり方は自己記述的なものではありません。そのため、デコードを行う際には、あるスキーマで行うことが必要になります。
コントラクトのインターフェースは型付けされていることが強制されます。このことはコンパイル時に静的型付けであることとしてすでに知られていることです。すべてのコントラクトは、コンパイル時に呼び出し可能なすべてのコントラクトのインターフェース定義を内包します。
この仕様は動的なインターフェースやランタイムにのみ認識されるインターフェースを持つコントラクトには対応しません。
Function selector¶
関数呼び出し時のコールデータの最初の4バイトは呼び出す関数を指定しています。関数シグネチャにおけるKeccak-256(SHA-3)ハッシュの最初の4バイト(左から4バイトでビッグ・エンディアンの順序になっています)のことです。このシグネチャはデータ位置指定子(data location specifier)を含まない基本プロトタイプの正規表現として定義されています。例えば、かっこで囲まれたパラメータ型のリストを含む関数名などが挙げられます。パラメータの型は単一のコンマで区切られ、スペースは使用されません。
注釈
関数の戻り値の型はこのシグニチャの一部ではありません。 Solidityの関数のオーバーロード においては、戻り値の型は考慮されません。これは関数呼び出しを特定のコンテキストに依存しないようにするためです。 しかし、ABIのJSON記述 は入力値と出力値の両方を含むことに注意が必要です。
Argument Encoding¶
先頭から数えて5バイト目から、エンコーディングされた引数が続きます。このエンコーディングされた引数は、特定の関数を指定する4バイトを除いて、戻り値などの別の場所でも使用されます。また、イベント引数も同やり方でエンコーディングされます。
Types¶
基本の型は以下です:
uint<M>
:M
ビットのunsigned int型。0 < M <= 256
とM % 8 == 0
である必要があります。uint32
、uint8
、uint256
が挙げられます。int<M>
: 2の補数がつくM
ビットのsigned int型。0 < M <= 256
、M % 8 == 0
である必要があります。address
:uint160
と同等です。ただしこの場合、uint160
が暗黙的な値や型であるときを除きます。関数セレクタを計算する際に、address
は使用されます。uint
,int
:uint256
やint256
と同じ意味です。関数セレクタを計算する際に、uint256
とint256
は必ず使用されなくてはなりません。bool
:uint8
と同等です。ただしこの場合、値は0と1のみである必要があります。 関数セレクタを計算する際に、bool
が使用されます。fixed<M>x<N>
:M
ビットの符号付き固定小数点。8 <= M <= 256
、M % 8 ==0
、0 < N <= 80
である必要があり、v
asv / (10 ** N)
であることを示します。ufixed<M>x<N>
:fixed<M>x<N>
の符号付き変数。fixed
,ufixed
:fixed128x18
やufixed128x18
と同じ意味です。関数セレクタを計算する際に、fixed128x18
とufixed128x18
は必ず使用されなくてはなりません。bytes<M>
:M
バイトのバイナリ型。0 < M <= 32
です。function
: アドレス(20バイト)とそれに続く関数セレクタ(4バイト)。bytes24
と同じエンコーディングです。
固定長な型は以下です:
<type>[M]
:M
個要素の固定長配列です。与えられた型のM >= 0
になります。
可変長な型は以下です:
bytes
: 動的サイズのバイトシーケンス。string
: UTF-8でエンコードされている動的サイズのUnicode文字列。<type>[]
: 指定されたタイプの要素の可変長配列。
データ型はカンマで区切って括弧で囲むとタプルにまとめることができます:
(T1,T2,...,Tn)
: 同じデータ型T1
...,Tn
,n >= 0
で構成されたタプル。
また、タプルのタプル、タプルの配列なども形成することができます。ゼロタプル( n == 0
)を作ることもできます。
Mapping Solidity to ABI types¶
Solidityは上記のすべての型を同じ名前でサポートしています(ただしタプルは例外です)。一方で、SolidityタイプのいくつかはABIにはサポートされていません。次の表は、左列にABIにサポートされていないSolidityのデータ型を、右列にはABI側でのそのデータ型の表記を表しています。
Solidity | ABI |
---|---|
address payable | address |
contract | address |
enum | smallest For example, an |
struct | tuple |
Design Criteria for the Encoding¶
エンコーディングは次のプロパティを持つように設計されています。これは、引数がネストされた配列の場合に特に便利です。
- 値にアクセスするのに必要な読み込みの数は、引数となる配列構造内の値の最大の深さに依ります。すなわち、
a_i [k] [l] [r]
を取得するには4回の読み込みが必要になるということです。以前のバージョンのABIでは、最悪の場合、読み取り数は動的パラメータの総数に比例して増減しました。- 変数または配列要素のデータは他のデータとインターリーブされません。またこれらのデータは再配置可能です。つまり、相対的な「アドレス」のみを使用します。
Formal Specification of the Encoding¶
静的型と動的型を区別するようにします。静的型はインプレースでエンコードされ、動的型はカレントブロック後に別々に割り当てられた場所でエンコードされます。
定義: 次の型は「動的型」です:
bytes
string
- 任意の
T
におけるT[]
- 任意の動的な
T
と任意のk >= 0
におけるT[k]
Ti
が1 <= i <= k
に対して動的である場合の(T1,...,Tk)
上記以外のすべてのすべての型は「静的型」です。
定義: len(a)
はバイナリ文字列 a
のバイト数です。len(a)
の型は uint256
であると仮定されます。
実際のエンコーディングである enc
は、ABI型の値を以下のようなバイナリ文字列にマッピングするものとして定義します。len(enc(X))
は X
の型が動的である場合に限り、X
の値に依存します。
定義: 任意のABI値 X
に対して、下記の X
の型によって再帰的に enc(X)
を定義します。
k >= 0
かつ任意の型T1
, ...,Tk
における(T1,...,Tk)
のときは以下の様になります:enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))
Ti
が静的型である場合、X = (X(1)), ..., X(k))
とhead
とtail
は以下のようにhead(X(i)) = enc(X(i))
およびtail(X(i)) = ""
(空の文字列)や
head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)) ))
tail(X(i)) = enc(X(i))
となります。
上記以外の場合は、
Ti
は動的型です。動的な場合、
head(X(i))
は ヘッド部分がそのデータ型にのみ依存し、値には依存しません。またその値はenc(X)
の先頭からtail(X(i))
の先頭までの範囲でのオフセットです。任意の
T
とk
におけるT[k]
のときは以下になります:enc(X) = enc((X[0], ..., X[k-1]))
すなわち、同じ型の
k
要素を持つタプルであるかのようにエンコードされます。X
がk
要素を持つとき(k
はuint256
型であると仮定されます)のT[]
のときは以下になります:
enc(X) = enc(k) enc([X[0], ..., X[k-1]])
すなわち、要素数を前に付けた静的サイズの
k
の配列であるかのようにエンコードされます。
長さが
k
(これはuint256
型であると仮定されます)のbytes
のときは以下になります:enc(X) = enc(k) pad_right(X)
。すなわち、バイト数はuint256
の後にバイトシーケンスとしてのX
の実際の値が続き、その後にlen(enc(X))
が32の倍数になるような最小ゼロバイト数が続きます。string
のときは以下になります:enc(X) = enc(enc_utf8(X))
。すなわち、X
はUTF-8でエンコードされており、この値はbytes
型として解釈され、さらにエンコードされます。この後のエンコーディングで使用される長さは、その文字数ではなく、utf-8でエンコードされた文字列のバイト数です。uint<M>
:enc(X)
はX
のビッグエンディアンエンコーディングで、長さが32バイトになるように高位(左側)にゼロバイトが埋め込まれます。address
:uint160
の場合と同じです。int<M>
:enc(X)
は、X
のビッグエンディアンの2の補数エンコーディングで、負のX
の場合は高位(左側)に0xff
がパディングされ、長さが32バイトになるような正のX
の場合はゼロバイトがパディングされます。bool
:uint8
の場合のように、1
はtrue
が使われ、0
はfalse
が使われます。fixed<M>x<N>
:enc(X)
はenc(X * 10**N)
です。ここでX * 10**N
はint256
として解釈されます。fixed
:fixed128x18
の場合と同じです。ufixed<M>x<N>
:enc(X)
はenc(X * 10**N)
です。ここでX * 10**N
はuint256
として解釈されます。ufixed
:fixed128x18
の場合と同じです。bytes<M>
:enc(X)
は末尾の0バイトを32バイトの長さまで埋め込んだX
内のバイトの並びです。
任意のX
において、len(enc(X))
は32の倍数であることを念頭に入れてください。
Function Selector and Argument Encoding¶
たいていの場合、a_1, ..., a_n
をパラメータとして関数 f
をコールするときには次のようにエンコーディングされます。
function_selector(f) enc((a_1, ..., a_n))
また、f
の返り値である v_1, ..., v_k
は次のようにエンコーディングされます。
enc((v_1, ..., v_k))
すなわち、値はタプルに組み合わされてエンコードされます。
Examples¶
次のコントラクトがあります:
pragma solidity >=0.4.16 <0.6.0;
contract Foo {
function bar(bytes3[2] memory) public pure {}
function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
function sam(bytes memory, bool, uint[] memory) public pure {}
}
このように Foo
の例では、もしパラメータに「69」と「true」を指定して「baz」を呼び出すなら、合計68バイトを渡します。このことは次のように分割できます。
0xcdcd77c0
: 指定のメソッドID。これはbaz(uint32,bool)
の署名のASCII形式のKeccakハッシュにおける最初の4バイトとして導出されます。0x0000000000000000000000000000000000000000000000000000000000000045
: 32バイトにパディングされた最初のパラメータ69
のuint32値。0x0000000000000000000000000000000000000000000000000000000000000001
: 32バイトにパディングされた2番目のパラメータtrue
のブール値。
これらを連結すると:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
一つの bool
を返します。たとえば、false
を返すことになっていたら、その出力はシングルバイト配列 0x000000000000000000000000000000000000000000000000000000000000
、シングルブールになります。
これは1つの bool
値を返します。 もしここで例えばこの値が false
を返すなら、その出力はバイト配列の 0x0000000000000000000000000000000000000000000000000000000000000000
となるでしょう。
引数 ["abc"、"def"]
と共に bar
を呼び出したい場合は、合計68バイトを渡します。これは次のように分割されます:
0xfce353f6
: 指定のメソッドID。これはbar(bytes3[2])
の署名から導出されます。0x6162630000000000000000000000000000000000000000000000000000000000
: 最初の引数の最初のパート、bytes3
の値"abc"
(左寄せ)。0x6465660000000000000000000000000000000000000000000000000000000000
: 最初の引数の2番目のパート、bytes3
の値"def"
(左寄せ)。
これらを連結すると:
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
引数として "dave"
、true
および [1,2,3]
を付けて sam
を呼び出したい場合、合計292バイトを渡します。
0xa5643bf2
: 指定のメソッドID。 これはsam(bytes,bool,uint256[])
の署名から導出されます。uint
はその正規表現uint256
に置き換えられていることを念頭に入れてください。0x0000000000000000000000000000000000000000000000000000000000000060
: 引数ブロックの先頭からのバイト単位で測定された、最初のパラメータ(動的型)のデータ部分の位置。この場合は0x60
です。0x0000000000000000000000000000000000000000000000000000000000000001
: 2番目のパラメータ:true
のブール値。0x00000000000000000000000000000000000000000000000000000000000000a0
: 3番目のパラメータ(動的型)のデータ部分の位置(バイト単位)。この場合、0xa0
です。0x0000000000000000000000000000000000000000000000000000000000000004
: 最初の引数のデータ部分。要素のバイト配列の長さで始まります。この場合は4です。0x6461766500000000000000000000000000000000000000000000000000000000
: 最初の引数の内容:32バイトまで右側にパディングしたUTF-8(この場合はASCIIに等しくなります)エンコーディング。0x0000000000000000000000000000000000000000000000000000000000000003
: 3番目の引数のデータ部分。要素の配列の長さから始まります。この場合は3です。0x0000000000000000000000000000000000000000000000000000000000000001
: 3番目のパラメータの最初のデータ0x0000000000000000000000000000000000000000000000000000000000000002
: 3番目のパラメータの2番目のデータ0x0000000000000000000000000000000000000000000000000000000000000003
: 3番目のパラメータの3番目のデータ
これらを連結すると:
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
Use of Dynamic Types¶
(0x123、[0x456、0x789]、"1234567890"、"Hello、world!")
の値を持つシグネチャf(uint、uint32[]、bytes10、bytes)
を持つ関数コールは次のようにエンコードされます:
sha3("f(uint256,uint32[],bytes10,bytes)")
の最初の4バイトを取ります。すなわち、0x8be65246
です。
それから、4つの引数すべての先頭部分をエンコードします。静的型である uint256
と bytes10
の場合、これらは直接渡したい値ではありますが、動的型の uint32[]
と bytes
の場合は、値エンコーディングの開始から測定された、データ領域の開始までのバイト単位のオフセットを使用します(つまり、関数シグネチャのハッシュを含む最初の4バイトはカウントしません)。これらは:
0x0000000000000000000000000000000000000000000000000000000000000123
(32バイトにパディングされた0x123
の値)0x0000000000000000000000000000000000000000000000000000000000000080
(2番目のパラメータのデータ部の先頭までのオフセット。4 * 32バイト。正確には先頭部分のサイズ)0x3132333435363738393000000000000000000000000000000000000000000000
(32バイトに右詰めにパディングされた"1234567890"
の値)0x00000000000000000000000000000000000000000000000000000000000000e0
(4番目のパラメータのデータ部の先頭までのオフセット = 最初の動的パラメータのデータ部の先頭までのオフセット + 最初の動的パラメータのデータ部のサイズ = 4 * 32 + 3 * 32(下記を参照してください)))
このあと、最初の動的引数のデータ部分である [0x456, 0x789]
は次のようになります:
0x0000000000000000000000000000000000000000000000000000000000000002
(配列の要素数、2)0x0000000000000000000000000000000000000000000000000000000000000456
(最初の要素)0x0000000000000000000000000000000000000000000000000000000000000789
(2番目の要素)
最後に、2番目の動的引数のデータ部分、"Hello, world!"
をエンコードします:
0x000000000000000000000000000000000000000000000000000000000000000d
(要素数(この場合バイトになります): 13)0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000
(32バイトになるように右側にパディングした"Hello, world!"
)
こうして、エンコーディングは次のとおりです(関数セレクタの後に改行、わかりやすくするために各32バイト):
0x8be65246
0000000000000000000000000000000000000000000000000000000000000123
0000000000000000000000000000000000000000000000000000000000000080
3132333435363738393000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000456
0000000000000000000000000000000000000000000000000000000000000789
000000000000000000000000000000000000000000000000000000000000000d
48656c6c6f2c20776f726c642100000000000000000000000000000000000000
([[1、2]、[3]]、["one"、"two"、"three"])
の値で署名付きの関数 g(uint[][],string[])
に同じ原則を適用します。しかし、エンコーディングの最も基本的な部分から始めましょう:
まずはじめに、最初のルート配列 [[1, 2], [3]]
の最初の動的な要素配列 [1, 2]
の長さとデータをエンコードします。
0x0000000000000000000000000000000000000000000000000000000000000002
(最初の配列の要素数である2。ただし要素自体は1と2です)0x0000000000000000000000000000000000000000000000000000000000000001
(最初の要素)0x0000000000000000000000000000000000000000000000000000000000000002
(2番目の要素)
次に、最初のルート配列 [[1、2]、[3]]
の2番目の動的な要素配列 [3]
の長さとデータをエンコードします。
0x0000000000000000000000000000000000000000000000000000000000000001
(2番目の配列の要素数である1。要素は3です)0x0000000000000000000000000000000000000000000000000000000000000003
(最初の要素)
それから、それぞれの動的配列 [1、2]
と [3]
に対するオフセット a
と b
を見つける必要があります。オフセットを計算するために、エンコーディングの各行を列挙している最初のルート配列 [[1、2]、[3]]
のエンコードされたデータを見ることができます。
0 - a - [1, 2]のオフセット
1 - b - [3]のオフセット
2 - 0000000000000000000000000000000000000000000000000000000000000002 - [1, 2]の要素数
3 - 0000000000000000000000000000000000000000000000000000000000000001 - 1のエンコーディング
4 - 0000000000000000000000000000000000000000000000000000000000000002 - 2のエンコーディング
5 - 0000000000000000000000000000000000000000000000000000000000000001 - [3]の要素数
6 - 0000000000000000000000000000000000000000000000000000000000000003 - 3のエンコーディング
オフセット a
は、2行目(64バイト)の配列 [1、2]
の始まりを指しています。したがって、a = 0x0000000000000000000000000000000000000000000000000000000000000040
です。
オフセット b
は、配列 [3]
の5行目(160バイト)の先頭を指します。したがって、b = 0x00000000000000000000000000000000000000000000000000000000000000a0
です。
そして、2番目のルート配列の埋め込み文字列をエンコードします:
0x0000000000000000000000000000000000000000000000000000000000000003
(単語"one"
の中の文字数)0x6f6e650000000000000000000000000000000000000000000000000000000000
(単語"one"
のutf8での表現)0x0000000000000000000000000000000000000000000000000000000000000003
(単語"two"
の中の文字数)0x74776f0000000000000000000000000000000000000000000000000000000000
(単語"two"
のutf8での表現)0x0000000000000000000000000000000000000000000000000000000000000005
(単語"three"
の中の文字数)0x7468726565000000000000000000000000000000000000000000000000000000
(単語"three"
のutf8での表現)
最初のルート配列と並行して、文字列は動的要素なので、それらのオフセット c
、d
および e
を見つける必要があります:
0 - c - "one"のオフセット
1 - d - "two"のオフセット
2 - e - "three"のオフセット
3 - 0000000000000000000000000000000000000000000000000000000000000003 - "one"の文字列数
4 - 6f6e650000000000000000000000000000000000000000000000000000000000 - "one"のエンコーディング
5 - 0000000000000000000000000000000000000000000000000000000000000003 - "two"の文字列数
6 - 74776f0000000000000000000000000000000000000000000000000000000000 - "two"のエンコーディング
7 - 0000000000000000000000000000000000000000000000000000000000000005 - "three"の文字列数
8 - 7468726565000000000000000000000000000000000000000000000000000000 - "three"のエンコーディング
オフセット c
は、文字列 "one"
の3行目(96バイト)の先頭を指します。したがって、c = 0x0000000000000000000000000000000000000000000000000000000000000060
です。
オフセット d
は、文字列 "two"
の先頭を指します。これは5行目(160バイト)です。したがって、d = 0x00000000000000000000000000000000000000000000000000000000000000a0
です。
オフセット e
は、文字列 "three"
の7行目(224バイト)の先頭を指します。したがって、e = 0x00000000000000000000000000000000000000000000000000000000000000e0
です。
ルート配列の埋め込み要素のエンコーディングは互いに依存関係がなく、シグネチャが g(string[],uint[][])
の関数に対して同じエンコーディングを持つことに注意してください。
次に、最初のルート配列の長さをエンコードします:
0x0000000000000000000000000000000000000000000000000000000000000002
(最初のルート配列の要素数である2。要素自体は[1, 2]
と[3]
です)
次に、2番目のルート配列の長さをエンコードします:
0x0000000000000000000000000000000000000000000000000000000000000003
(2番目のルート配列の文字列数である3。文字列自体は"one"
、"two"
及び"three"
です)
最後に、それぞれのルート動的配列 [[1, 2], [3]]
と ["one", "two", "three"]
に対するオフセット f
と g
を見つけます。そして、正しい順序でこれらのパーツを組み立てます。
0x2289b18c - 関数シグニチャ
0 - f - [[1, 2], [3]]のオフセット
1 - g - ["one", "two", "three"]のオフセット
2 - 0000000000000000000000000000000000000000000000000000000000000002 - [[1, 2], [3]]の要素数
3 - 0000000000000000000000000000000000000000000000000000000000000040 - [1, 2]のオフセット
4 - 00000000000000000000000000000000000000000000000000000000000000a0 - [3]のオフセット
5 - 0000000000000000000000000000000000000000000000000000000000000002 - [1, 2]の要素数
6 - 0000000000000000000000000000000000000000000000000000000000000001 - 1のエンコーディング
7 - 0000000000000000000000000000000000000000000000000000000000000002 - 2のエンコーディング
8 - 0000000000000000000000000000000000000000000000000000000000000001 - [3]の要素数
9 - 0000000000000000000000000000000000000000000000000000000000000003 - 3のエンコーディング
10 - 0000000000000000000000000000000000000000000000000000000000000003 - ["one", "two", "three"]の要素数
11 - 0000000000000000000000000000000000000000000000000000000000000060 - "one"のエンコーディング
12 - 00000000000000000000000000000000000000000000000000000000000000a0 - "two"のオフセット
13 - 00000000000000000000000000000000000000000000000000000000000000e0 - "three"のオフセット
14 - 0000000000000000000000000000000000000000000000000000000000000003 - "one"の文字列数
15 - 6f6e650000000000000000000000000000000000000000000000000000000000 - "one"のエンコーディング
16 - 0000000000000000000000000000000000000000000000000000000000000003 - "two"の文字列数
17 - 74776f0000000000000000000000000000000000000000000000000000000000 - "two"のエンコーディング
18 - 0000000000000000000000000000000000000000000000000000000000000005 - "three"の文字列数
19 - 7468726565000000000000000000000000000000000000000000000000000000 - "three"のエンコーディング
オフセット f
は、配列 [[1、2]、[3]]
の2行目(64バイト)の先頭を指します。したがって、f = 0x0000000000000000000000000000000000000000000000000000000000000040
です。
オフセット g
は、配列 ["one"、"two"、"three"]
の先頭を指します。これは10行目(320バイト)です。したがって、g = 0x0000000000000000000000000000000000000000000000000000000000000140
です。
Events¶
イベントはEthereumロギング/イベント監視プロトコルを抽象化したものです。ログエントリは、コントラクトアドレス、最大4つまでの一連のトピック、及び任意の長さのバイナリデータを提供します。イベントは既存の関数ABIを(インターフェース仕様とともに)正しく型付けされた構造体として解釈するために利用します。
イベント名と一連のイベントパラメータが与えられたとき、それらを2つのサブシリーズに分けます。それはインデックスされているものとそうでないものです。インデックス付けされたもの(最大3まで)は、イベント署名のKeccakハッシュと一緒に使用されて、ログエントリのトピックを形成します。インデックスが付けられていないものはイベントのバイト配列を形成します。
実際には、このABIを使用するログエントリは次のように記述されています:
address
: コントラクトアドレス(Ethereumから提供されるものです)topics[0]
:keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")
(canonical_type_of
は与えられた引数の標準型を単純に返す関数です。uint indexed foo
の場合は、uint256
を返します。) イベントが匿名
として宣言されている場合、topics[0]
は生成されません。topics[n]
:EVENT_INDEXED_ARGS[n - 1]
(EVENT_INDEXED_ARGS
はインデックスが付けられた一連のEVENT_ARGS
です)data
:abi_serialise(EVENT_NON_INDEXED_ARGS)
(EVENT_NON_INDEXED_ARGS
はインデックスされていない一連のEVENT_ARGS
、abi_serialise
は上述のように関数から一連の型付き値を返すために使用されるABIシリアライゼーション関数です)
すべての固定長のSolidityの型において、EVENT_INDEXED_ARGS
配列は32バイトでエンコードされた値を直接含みます。しかし、string
、bytes
、配列を含む 動的長 型の場合、EVENT_INDEXED_ARGS
はパックされたエンコード値の Keccakハッシュ を含みます( Strict Encoding Mode を参照ください)。これにより、アプリケーションは(エンコードされた値のハッシュをトピックとして設定することによって)動的長の型の値を効率的に照会できますが、照会していない索引付きの値をデコードできません。動的長型の場合、アプリケーション開発者は、事前定義された値の高速検索(引数がインデックス付けされている場合)と任意の値の読みやすさ(引数にインデックスが付けられていないことが必要)との間のトレードオフに直面します。開発者はこのトレードオフを克服し、同じ値を保持することを目的とした2つの引数(1つはインデックス付き、1つはない)でイベントを定義することによって、効率的な検索と任意の読みやすさの両方を実現できます。
JSON¶
コントラクトインターフェースのJSONフォーマットは、関数やイベントのデスクリプションの配列によって指定されます。
関数の説明はフィールドを持つJSONオブジェクトです:
type
:"function"
、"constructor"
、及び"fallback"
( unnamed "default" function)name
: 関数の名前inputs
: それぞれが含むオブジェクトの配列:name
: パラメータの名前type
: パラメータの標準型(詳細は後述)components
: タプル型に使用されます(詳細は後述)
outputs
: functionが何も返さない場合は、input
に似たオブジェクトの配列を省略することができます。stateMutability
: 以下のいずれかの値を持つ文字列:pure
(specified to not read blockchain state),view
(specified to not modify the blockchain state),nonpayable
(function does not accept Ether) andpayable
(function accepts Ether);payable
: もし関数がEtherを受け付けるなら、true
。そうでないなら、false
。constant
: もし関数がpure
かview
のいづれかなら、true
。そうでないなら、false
。
type
は省略することができ、"function"
にデフォルト設定され、同様に payable
と constant
は省略することができ、両方とも false
にデフォルト設定されます。
コンストラクタとフォールバック関数は、name
や outputs
などを持つことはありません。フォールバック関数も inputs
を持つこともありません。
警告
constant
や payable
は今後廃止予定であり、今後取り除かれます。代わりに、stateMutability
領域が今後は同じプロパティを決定するために使うことができます。
注釈
ゼロ以外の額のイーサリアムをnon-payable functionに送金するときは、トランザクションにリバートが発生します。
イベントの詳細は似たフィールドを持つJSONオブジェクトです:
type
: 常に"event"
です。name
: イベントの名前;inputs
: 以下の値を含むオブジェクトの配列:name
: パラメータの名前type
: 標準的なパラメータの型 (詳細は以下を参照してください)components
: タプルに使用する(詳細は以下を参照してください)indexed
: もしフィールドがログトピックスの一部であるなら、true
。もしログデータセグメントであるならfalse
anonymous
: もしイベントがanonymous
と宣言されていたならtrue
例えば、
pragma solidity ^0.5.0;
contract Test {
constructor() public { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); }
bytes32 b;
}
これはJSONで表現すると以下のようになります:
[{
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event2"
}, {
"type":"function",
"inputs": [{"name":"a","type":"uint256"}],
"name":"foo",
"outputs": []
}]
Handling tuple types¶
名前(names)はABIエンコーディングの一部にあたるわけではないですが、ABIに含めることには意味があります。これはJSON形式でエンドユーザーに表示できるという点にあります。 その構造は次のようにネストされています:
name
やtype
、潜在的にはcomponents
などと共にオブジェクトは型付きの変数を記述します。
正規型はタプル型に達するまで決定され、それまでの文字列の説明は type
プレフィックスに tuple
という語で格納されます。つまり、tuple
の後に整数の k
を持つ []
と [k]
のシーケンスが続きます。
タプルの構成要素はメンバの構成要素に格納されます。これは配列型で、インデックスが許可されていないことを除いて最上位オブジェクトと同じ構造を持ちます。
例として、次のコードは
pragma solidity >=0.4.19 <0.6.0;
pragma experimental ABIEncoderV2;
contract Test {
struct S { uint a; uint[] b; T[] c; }
struct T { uint x; uint y; }
function f(S memory s, T memory t, uint a) public;
function g() public returns (S memory s, T memory t, uint a);
}
以下のようなJSONとなります:
[
{
"name": "f",
"type": "function",
"inputs": [
{
"name": "s",
"type": "tuple",
"components": [
{
"name": "a",
"type": "uint256"
},
{
"name": "b",
"type": "uint256[]"
},
{
"name": "c",
"type": "tuple[]",
"components": [
{
"name": "x",
"type": "uint256"
},
{
"name": "y",
"type": "uint256"
}
]
}
]
},
{
"name": "t",
"type": "tuple",
"components": [
{
"name": "x",
"type": "uint256"
},
{
"name": "y",
"type": "uint256"
}
]
},
{
"name": "a",
"type": "uint256"
}
],
"outputs": []
}
]
Strict Encoding Mode¶
Strict encoding modeは上記の仕様で定義されているものとまったく同じ符号化につながるモードです。 これは、データ領域にオーバーラップが発生しないようにしながらオフセットをできるだけ小さくする必要があるため、あらゆるギャップが許容されないことを意味します。
大抵の場合、ABIデコーダはオフセットポインタの直後に書かれていますが、Strict encoding modeを強制するデコーダもあります。 SolidityのABIデコーダは現在Strict encoding modeを強制しませんが、エンコーダは常にStrict encoding modeでデータを作成します。
Non-standard Packed Mode¶
abi.encodePacked()
を通じて、SolidityはNon-standard Packed Modeをサポートしています:
- 32バイトより短い型はゼロパディングも符号拡張もされず、
- 動的型はその場でlengthを持たずにエンコードされます
Non-standard Packed Modeは主にインデックス付きイベントパラメータに使用されます。
例として、int16(-1), bytes1(0x42), uint16(0x03), string("Hello, world!")
のエンコーディングは次のようになります:
0xffff42000348656c6c6f2c20776f726c6421
^^^^ int16(-1)
^^ bytes1(0x42)
^^^^ uint16(0x03)
^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length field
- より詳細にいうと:
- 各値型は、その範囲と同じ数のバイトを取ります。
- 構造体または固定長配列のエンコーディングは、そのメンバー/要素のエンコーディングを連結したもので、区切り文字やパディングはありません。
- 構造体のマッピングメンバーは通常どおり認識されません。
string
、bytes
、uint []
のような動的型はそれらのlengthフィールドなしでエンコードされます。
一般に、lengthフィールドが欠落しているため、2つの動的な要素があるとすぐに、エンコードはあいまいなものになってしまいます。
もしパディングが必要であれば、次のような明示的な型変換が使えるでしょう: abi.encodePacked(uint16(0x12)) == hex"0012"
パックエンコーディングは関数呼び出しの際に使われるものではないため、関数セレクタを準備するためのサポートがあるわけではありません。 また、エンコーディングが曖昧であるため、復号化のための関数は存在しません。
Yul¶
Yul(以前JULIAやIULIAと呼ばれていたものです)は複数の異なるバックエンド(EVM1.0やEVM1.5、eWASM)でコンパイルできる中間言語です。 そのため、Yulは3つのプラットフォームすべての使用可能な共通分母になるように設計されています。 YulはすでにSolidity内の"inline assembly"としても使用されており、Solidityコンパイラの将来のバージョンではYulを中間言語として使用できるようになります。 また、Yul用の高水準オプティマイザステージを構築するのは簡単です。
注釈
"inline assembly"として使用されるときに型を持つわけではありません(すべて u256
になります)。
そして、組み込み関数はEVMオペコードと同じです。詳細についてはinline assemblyのドキュメントを参照してください。
Yulのコアコンポーネントは関数、ブロック、変数、リテラル、forループ、if文、switch文、式と変数への代入です。
Yulは型付けされ、変数とリテラルの両方が後置記法で型を指定しなければなりません。
サポートしている型は、 bool
、 u8
、 s8
、 u32
、 s32
、 u64
、 s64
、 u128
、 s128
、 u256
、 s256
です。
Yul自体は、演算子すら持ちません。もしあるEVMが指定された場合、オペコードは組み込み関数として利用可能になりますが、バックエンドが変更された場合は再実装することができます。 マンダトリーなビルドイン関数のリストは下のセクションを参照ください。
次のプログラムの例では、EVMのオペコード mul
、 div
、および mod
がネイティブでも関数としても利用可能であると仮定して、べき乗を計算します。
{
function power(base:u256, exponent:u256) -> result:u256
{
switch exponent
case 0:u256 { result := 1:u256 }
case 1:u256 { result := base }
default
{
result := power(mul(base, base), div(exponent, 2:u256))
switch mod(exponent, 2:u256)
case 1:u256 { result := mul(base, result) }
}
}
}
また、再帰ではなくforループを使用して同じ関数を実装することも可能です。
ここで、利用可能なEVMオペコードの lt
(less-than)と add
が必要になります。
{
function power(base:u256, exponent:u256) -> result:u256
{
result := 1:u256
for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) }
{
result := mul(result, base)
}
}
}
Specification of Yul¶
この章では、Yulのコードを記述します。通常、Yulオブジェクトの中に置かれるものです。これについては次の章で説明します。
Grammar:
Block = '{' Statement* '}'
Statement =
Block |
FunctionDefinition |
VariableDeclaration |
Assignment |
If |
Expression |
Switch |
ForLoop |
BreakContinue
FunctionDefinition =
'function' Identifier '(' TypedIdentifierList? ')'
( '->' TypedIdentifierList )? Block
VariableDeclaration =
'let' TypedIdentifierList ( ':=' Expression )?
Assignment =
IdentifierList ':=' Expression
Expression =
FunctionCall | Identifier | Literal
If =
'if' Expression Block
Switch =
'switch' Expression ( Case+ Default? | Default )
Case =
'case' Literal Block
Default =
'default' Block
ForLoop =
'for' Block Expression Block Block
BreakContinue =
'break' | 'continue'
FunctionCall =
Identifier '(' ( Expression ( ',' Expression )* )? ')'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
IdentifierList = Identifier ( ',' Identifier)*
TypeName = Identifier | BuiltinTypeName
BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' )
TypedIdentifierList = Identifier ':' TypeName ( ',' Identifier ':' TypeName )*
Literal =
(NumberLiteral | StringLiteral | HexLiteral | TrueLiteral | FalseLiteral) ':' TypeName
NumberLiteral = HexNumber | DecimalNumber
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
TrueLiteral = 'true'
FalseLiteral = 'false'
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+
Restrictions on the Grammar¶
Switch文はdefault caseを含む最低でも1つのcaseを持ちます。
もし式のすべての可能な値がカバーされている場合、defaultのcaseは許容するべきではありません(すなわち、真と偽の両方のケースを持つ bool
を持つSwitch文)。
また、すべてのcaseの値は同じ型である必要があります。
すべての式はゼロ以上の値に評価されます。識別子とリテラルは厳密に1つの値であると評価され、関数呼び出しは呼び出された関数の戻り値の数と等しい数の値であると評価されます。
変数宣言時や代入時、右辺の式は、左辺の変数の数と等しい数の値であると評価される必要があります。 これが、複数の値に評価される式が許可される唯一の状況です。
ステートメント(すなわち、ブロックレベル)でもある式は、0として評価される必要があります。 そして、その他のすべてのシチュエーションにおいて、式は単一の値として評価する必要があります。
continue
やbreak
文はループ文中でのみ使用でき、ループ文と同じ関数内になければなりません(もしくは両方ともトップレベルになければなりません)。
for文の条件は、単一の値で評価される必要があります。
リテラルは、その型以上に大きくなることはありません。最大値の型は、256ビット長であることが決められています。
Scoping Rules¶
Yulのスコープはブロックと結びついており(関数とforループは例外です)、すべての宣言( FunctionDefinition
、 VariableDeclaration
)はこれらのスコープに新たな識別子をもたらします。
識別子は、定義されたブロック内で表示することができます(すべてのサブノートやサブブロックを含みます)。 例外として、forループの"init"部分(最初のブロック)で定義された識別子は、forループの他のすべての部分で表示することができます(ただし、ループの外側では表示できません)。 関数のパラメータと戻り値は関数本体に表示され、それらの名前は同じものを使用することはできません。
変数は宣言後に参照することができます。特に、変数はそれ自身の変数宣言の右側では参照できません。 関数は宣言前にすでに参照できます(可視性である場合に限ります)。
シャドーイングは使用できません。つまり、たとえアクセスできない場合でも、同じ名前の別の識別子も表示されている場所で識別子を宣言することはできません。
また、関数の外側で宣言された変数にアクセスすることはできません。
Formal Specification¶
私たちは、ASTのさまざまなノードにオーバーロードされた評価関数Eを提供して、Yulを形式的に指定します。 どの関数にも副作用がある可能性があるため、評価関数Eは2つのステートオブジェクトとASTノードを取り、2つの新しいステートオブジェクトと可変数の他の値を返します。 2つの状態オブジェクトとはグローバルステートオブジェクト(EVMのコンテキストではブロックチェーンのメモリ、ストレージ、およびステート)とローカルステートオブジェクト(ローカル変数のステート、つまりEVM内のスタックのセグメント)です。
もしASTノードがステートメントである場合、評価関数Eは2つのステートオブジェクトと、 break
と continue
に使用される "mode"を返します。
もしASTノードが式である場合、評価関数Eは2つのステートオブジェクトと式が評価する同じ数の値を返します。
グローバルステートの正確な性質は、この上位レベルの説明では規定されていません。
ローカルステート L
は、識別子 i
から値 v
へのマッピングで、L[i] = v
と表されます。
識別子 v
の場合、 $v
を識別子の名前とします。
また、ASTノードには分割表記を使用します。
E(G, L, <{St1, ..., Stn}>: Block) =
let G1, L1, mode = E(G, L, St1, ..., Stn)
let L2 be a restriction of L1 to the identifiers of L
G1, L2, mode
E(G, L, St1, ..., Stn: Statement) =
if n is zero:
G, L, regular
else:
let G1, L1, mode = E(G, L, St1)
if mode is regular then
E(G1, L1, St2, ..., Stn)
otherwise
G1, L1, mode
E(G, L, FunctionDefinition) =
G, L, regular
E(G, L, <let var1, ..., varn := rhs>: VariableDeclaration) =
E(G, L, <var1, ..., varn := rhs>: Assignment)
E(G, L, <let var1, ..., varn>: VariableDeclaration) =
let L1 be a copy of L where L1[$vari] = 0 for i = 1, ..., n
G, L1, regular
E(G, L, <var1, ..., varn := rhs>: Assignment) =
let G1, L1, v1, ..., vn = E(G, L, rhs)
let L2 be a copy of L1 where L2[$vari] = vi for i = 1, ..., n
G, L2, regular
E(G, L, <for { i1, ..., in } condition post body>: ForLoop) =
if n >= 1:
let G1, L1, mode = E(G, L, i1, ..., in)
// 構文上の制限のため、modeは規則的でなければなりません
let G2, L2, mode = E(G1, L1, for {} condition post body)
// 構文上の制限のため、modeは規則的でなければなりません
let L3 be the restriction of L2 to only variables of L
G2, L3, regular
else:
let G1, L1, v = E(G, L, condition)
if v is false:
G1, L1, regular
else:
let G2, L2, mode = E(G1, L, body)
if mode is break:
G2, L2, regular
else:
G3, L3, mode = E(G2, L2, post)
E(G3, L3, for {} condition post body)
E(G, L, break: BreakContinue) =
G, L, break
E(G, L, continue: BreakContinue) =
G, L, continue
E(G, L, <if condition body>: If) =
let G0, L0, v = E(G, L, condition)
if v is true:
E(G0, L0, body)
else:
G0, L0, regular
E(G, L, <switch condition case l1:t1 st1 ... case ln:tn stn>: Switch) =
E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {})
E(G, L, <switch condition case l1:t1 st1 ... case ln:tn stn default st'>: Switch) =
let G0, L0, v = E(G, L, condition)
// i = 1 .. n
// コンテキストに関係なくリテラルを評価します
let _, _, v1 = E(G0, L0, l1)
...
let _, _, vn = E(G0, L0, ln)
if there exists smallest i such that vi = v:
E(G0, L0, sti)
else:
E(G0, L0, st')
E(G, L, <name>: Identifier) =
G, L, L[$name]
E(G, L, <fname(arg1, ..., argn)>: FunctionCall) =
G1, L1, vn = E(G, L, argn)
...
G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2)
Gn, Ln, v1 = E(G(n-1), L(n-1), arg1)
Let <function fname (param1, ..., paramn) -> ret1, ..., retm block>
be the function of name $fname visible at the point of the call.
Let L' be a new local state such that
L'[$parami] = vi and L'[$reti] = 0 for all i.
Let G'', L'', mode = E(Gn, L', block)
G'', Ln, L''[$ret1], ..., L''[$retm]
E(G, L, l: HexLiteral) = G, L, hexString(l),
where hexString decodes l from hex and left-aligns it into 32 bytes
E(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l),
where utf8EncodeLeftAligned performs a utf8 encoding of l
and aligns it left into 32 bytes
E(G, L, n: HexNumber) = G, L, hex(n)
where hex is the hexadecimal decoding function
E(G, L, n: DecimalNumber) = G, L, dec(n),
where dec is the decimal decoding function
Type Conversion Functions¶
Yulは暗黙的型変換をサポートしていないため、明示的変換を提供するための関数が存在します。 大きな型からより小さな型へ変換するとき、オーバーフローの場合にruntime exceptionが発生する可能性があります。
- 以下の型間での変換の切り捨てがサポートされています:
bool
u32
u64
u256
s256
これらのそれぞれに対して、型変換関数は、 u32tobool(x:u32) -> y:bool
、 u256tou32(x:u256) -> y:u32
や s256tou256(x:s256) -> y:u256
などといった <input_type>to<output_type>(x:<input_type>) -> y:<output_type>
形式のプロトタイプを持ちます。
注釈
u32tobool(x:u32) -> y:bool
は y := not(iszerou256(x))
として実行され、
booltou32(x:bool) -> y:u32
は switch x case true:bool { y := 1:u32 } case false:bool { y := 0:u32 }
として実行されます。
Low-level Functions¶
以下の関数が利用可能でなければなりません:
Logic | |
not(x:bool) -> z:bool | logical not |
and(x:bool, y:bool) -> z:bool | logical and |
or(x:bool, y:bool) -> z:bool | logical or |
xor(x:bool, y:bool) -> z:bool | xor |
Arithmetic | |
addu256(x:u256, y:u256) -> z:u256 | x + y |
subu256(x:u256, y:u256) -> z:u256 | x - y |
mulu256(x:u256, y:u256) -> z:u256 | x * y |
divu256(x:u256, y:u256) -> z:u256 | x / y |
divs256(x:s256, y:s256) -> z:s256 | x / y, for signed numbers in two's complement |
modu256(x:u256, y:u256) -> z:u256 | x % y |
mods256(x:s256, y:s256) -> z:s256 | x % y, for signed numbers in two's complement |
signextendu256(i:u256, x:u256) -> z:u256 | sign extend from (i*8+7)th bit counting from least significant |
expu256(x:u256, y:u256) -> z:u256 | x to the power of y |
addmodu256(x:u256, y:u256, m:u256) -> z:u256 | (x + y) % m with arbitrary precision arithmetic |
mulmodu256(x:u256, y:u256, m:u256) -> z:u256 | (x * y) % m with arbitrary precision arithmetic |
ltu256(x:u256, y:u256) -> z:bool | true if x < y, false otherwise |
gtu256(x:u256, y:u256) -> z:bool | true if x > y, false otherwise |
lts256(x:s256, y:s256) -> z:bool | true if x < y, false otherwise (for signed numbers in two's complement) |
gts256(x:s256, y:s256) -> z:bool | true if x > y, false otherwise (for signed numbers in two's complement) |
equ256(x:u256, y:u256) -> z:bool | true if x == y, false otherwise |
iszerou256(x:u256) -> z:bool | true if x == 0, false otherwise |
notu256(x:u256) -> z:u256 | ~x, every bit of x is negated |
andu256(x:u256, y:u256) -> z:u256 | bitwise and of x and y |
oru256(x:u256, y:u256) -> z:u256 | bitwise or of x and y |
xoru256(x:u256, y:u256) -> z:u256 | bitwise xor of x and y |
shlu256(x:u256, y:u256) -> z:u256 | logical left shift of x by y |
shru256(x:u256, y:u256) -> z:u256 | logical right shift of x by y |
sars256(x:s256, y:u256) -> z:u256 | arithmetic right shift of x by y |
byte(n:u256, x:u256) -> v:u256 | nth byte of x, where the most significant byte is the 0th byte Cannot this be just replaced by and256(shr256(n, x), 0xff) and let it be optimised out by the EVM backend? |
Memory and storage | |
mload(p:u256) -> v:u256 | mem[p..(p+32)) |
mstore(p:u256, v:u256) | mem[p..(p+32)) := v |
mstore8(p:u256, v:u256) | mem[p] := v & 0xff - only modifies a single byte |
sload(p:u256) -> v:u256 | storage[p] |
sstore(p:u256, v:u256) | storage[p] := v |
msize() -> size:u256 | size of memory, i.e. largest accessed memory index, albeit due due to the memory extension function, which extends by words, this will always be a multiple of 32 bytes |
Execution control | |
create(v:u256, p:u256, n:u256) | create new contract with code mem[p..(p+n)) and send v wei and return the new address |
create2(v:u256, p:u256, n:u256, s:u256) | create new contract with code mem[p...(p+n)) at address
keccak256(0xff . this . s . keccak256(mem[p...(p+n)))
and send v wei and return the new address, where 0xff is a
8 byte value, this is the current contract's address
as a 20 byte value and s is a big-endian 256-bit value |
call(g:u256, a:u256, v:u256, in:u256, insize:u256, out:u256, outsize:u256) -> r:u256 | call contract at address a with input mem[in..(in+insize)) providing g gas and v wei and output area mem[out..(out+outsize)) returning 0 on error (eg. out of gas) and 1 on success |
callcode(g:u256, a:u256, v:u256, in:u256, insize:u256, out:u256, outsize:u256) -> r:u256 | identical to call but only use the code from a
and stay in the context of the
current contract otherwise |
delegatecall(g:u256, a:u256, in:u256, insize:u256, out:u256, outsize:u256) -> r:u256 | identical to callcode ,
but also keep caller
and callvalue |
abort() | abort (equals to invalid instruction on EVM) |
return(p:u256, s:u256) | end execution, return data mem[p..(p+s)) |
revert(p:u256, s:u256) | end execution, revert state changes, return data mem[p..(p+s)) |
selfdestruct(a:u256) | end execution, destroy current contract and send funds to a |
log0(p:u256, s:u256) | log without topics and data mem[p..(p+s)) |
log1(p:u256, s:u256, t1:u256) | log with topic t1 and data mem[p..(p+s)) |
log2(p:u256, s:u256, t1:u256, t2:u256) | log with topics t1, t2 and data mem[p..(p+s)) |
log3(p:u256, s:u256, t1:u256, t2:u256, t3:u256) | log with topics t, t2, t3 and data mem[p..(p+s)) |
log4(p:u256, s:u256, t1:u256, t2:u256, t3:u256, t4:u256) | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) |
State queries | |
blockcoinbase() -> address:u256 | current mining beneficiary |
blockdifficulty() -> difficulty:u256 | difficulty of the current block |
blockgaslimit() -> limit:u256 | block gas limit of the current block |
blockhash(b:u256) -> hash:u256 | hash of block nr b - only for last 256 blocks excluding current |
blocknumber() -> block:u256 | current block number |
blocktimestamp() -> timestamp:u256 | timestamp of the current block in seconds since the epoch |
txorigin() -> address:u256 | transaction sender |
txgasprice() -> price:u256 | gas price of the transaction |
gasleft() -> gas:u256 | gas still available to execution |
balance(a:u256) -> v:u256 | wei balance at address a |
this() -> address:u256 | address of the current contract / execution context |
caller() -> address:u256 | call sender (excluding delegatecall) |
callvalue() -> v:u256 | wei sent together with the current call |
calldataload(p:u256) -> v:u256 | call data starting from position p (32 bytes) |
calldatasize() -> v:u256 | size of call data in bytes |
calldatacopy(t:u256, f:u256, s:u256) | copy s bytes from calldata at position f to mem at position t |
codesize() -> size:u256 | size of the code of the current contract / execution context |
codecopy(t:u256, f:u256, s:u256) | copy s bytes from code at position f to mem at position t |
extcodesize(a:u256) -> size:u256 | size of the code at address a |
extcodecopy(a:u256, t:u256, f:u256, s:u256) | like codecopy(t, f, s) but take code at address a |
extcodehash(a:u256) | code hash of address a |
Others | |
discard(unused:bool) | discard value |
discardu256(unused:u256) | discard value |
splitu256tou64(x:u256) -> (x1:u64, x2:u64, x3:u64, x4:u64) | split u256 to four u64's |
combineu64tou256(x1:u64, x2:u64, x3:u64, x4:u64) -> (x:u256) | combine four u64's into a single u256 |
keccak256(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) |
Object access | |
datasize(name:string) -> size:u256 | size of the data object in bytes, name has to be string literal |
dataoffset(name:string) -> offset:u256 | offset of the data object inside the data area in bytes, name has to be string literal |
datacopy(dst:u256, src:u256, len:u256) | copy len bytes from the data area starting at offset src bytes to memory at position dst |
Backends¶
バックエンドやターゲットはYulから特定のバイトコードへのtranslatorとなります。各バックエンドは、バックエンドの名前を接頭辞として持つ関数を公開することができます。
また、バックエンドのために evm
と ewasm
の接頭辞を予約語としています。
Backend: EVM¶
EVMターゲットは evm_
接頭辞で公開されているすべての基底のEVMオペコードを持ちます。
Backend: "EVM 1.5"¶
TBD
Backend: eWASM¶
TBD
Specification of Yul Object¶
Yulオブジェクトは、名前付きコードとデータセクションをグループ化するために使用されます。
関数 datasize
、 dataoffset
および datacopy
は、コード内からこれらのセクションにアクセスするために使用できます。
また、16進数エンコーディングでデータを指定し、ネイティブエンコーディングで通常の文字列を指定するために16進数の文字列を使用できます。
コードの場合、 datacopy
はassembled binary representationにアクセスします。
Grammar:
Object = 'object' StringLiteral '{' Code ( Object | Data )* '}'
Code = 'code' Block
Data = 'data' StringLiteral ( HexLiteral | StringLiteral )
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
以上において、Block
は前の章で説明したYulコード文法の Block
を表します。
Yulオブジェクトの例を以下に示します:
// コードは単一のオブジェクトで構成されています。単一の "code"ノードはオブジェクトのコードです。
// すべての(他の)名前付きオブジェクトまたはデータセクションはシリアライズされ、
// 特別な組み込み関数 datacopy/dataoffset/datasize にアクセスできるようになります。
// ネストされたオブジェクトへのアクセスは"."を使って名前を結合することによって可能です。
// 現在のオブジェクト、現在のオブジェクト内のサブオブジェクト、およびデータ項目は、ネストアクセスなしでスコープ内にあります。
object "Contract1" {
code {
// 最初に "runtime.Contract2" を作成します
let size = datasize("runtime.Contract2")
let offset = allocate(size)
// 以下はeWASMの場合はmemory-> memory、
// EVMの場合は "runtime.Contract2"のコードコピーになります
datacopy(offset, dataoffset("runtime.Contract2"), size)
// コンストラクタパラメータは単一値0x1234になります
mstore(add(offset, size), 0x1234)
create(offset, add(size, 32))
// これでランタイムオブジェクトを返すようになりました(これはコンストラクタコードです)
size := datasize("runtime")
offset := allocate(size)
// 以下はeWASMの場合はmemory-> memory、EVMの場合はcodecopyになります。
datacopy(offset, dataoffset("runtime"), size)
return(offset, size)
}
data "Table2" hex"4123"
object "runtime" {
code {
// ランタイムコード
let size = datasize("Contract2")
let offset = allocate(size)
// 以下はeWASMの場合はmemory-> memory、EVMの場合はcodecopyになります。
datacopy(offset, dataoffset("Contract2"), size)
// コンストラクタパラメータは単一値0x1234になります
mstore(add(offset, size), 0x1234)
create(offset, add(size, 32))
}
// 埋め込みオブジェクト。ユースケースとして、オブジェクト外においてファクトリコントラクトであり、
// Contract2がファクトリによって作成されるコードです。
object "Contract2" {
code {
// コード...
}
object "runtime" {
code {
// コード...
}
}
data "Table1" hex"4123"
}
}
}
Style Guide¶
Introduction¶
このガイドの目的は、Solidityコードを書くためのコーディング規約を提供することです。 また、このガイドでは、みなさんが役に立つ規約が見つけられて、過去の規約が時代遅れとなれば、時間の経過とともに変化し進化するドキュメントとして考えてください。
多くのプロジェクトでは独自のスタイルガイドが実装されます。 つまり、コンフリクトが発生した場合は、プロジェクト固有のスタイルガイドが優先されるということです。
スタイルガイド内の構造と多くのレコメンデーションはpythonの pep8 style guide. をもとにしています。
また、このガイドの目的は、堅実なコードを書くための正しい方法や最良の方法を提供することではありません。 ガイドのゴールは、Solidityにおける consistency について知ってもらうことです。pythonの pep8 からの引用により、この目的を達成できるようします。
このスタイルガイドは一貫性(consistency)について述べます。このスタイルガイドのconsistencyは重要です。もっというと、プロジェクトのconsistencyはもっと重要です。 さらには、1つのモジュールにおけるconsistency、1つの関数におけるconsistencyはより重要なものです。 しかし最も重要なものとして: 一貫性のない(incosistentな)実装を行うほうが良い場合について知ることです。-- 時にはスタイルガイドが適用されないことがあります。疑わしいときは、あなたの最善の判断に従ってください。他の例を見て、最も良く見えるものを選んでください。そしてそうすることに躊躇しないでください!
Code Layout¶
Indentation¶
インデントには4つのスペースを使ってください。
Tabs or Spaces¶
スペースはタブよりもインデントに適しています。 また、スペースとタブの混合は避けるべきです。
Blank Lines¶
2行の空白行を持つSolidityソースでの最上位の宣言を囲むようにします。
Yes:
pragma solidity >=0.4.0 <0.6.0;
contract A {
// ...
}
contract B {
// ...
}
contract C {
// ...
}
No:
pragma solidity >=0.4.0 <0.6.0;
contract A {
// ...
}
contract B {
// ...
}
contract C {
// ...
}
コントラクト内では、関数宣言は1行の空白行で囲むようにします。 関連するワンライナーのグループ間で空白行を省略することができます(抽象コントラクトのスタブ関数など)。
Yes:
pragma solidity >=0.4.0 <0.6.0;
contract A {
function spam() public pure;
function ham() public pure;
}
contract B is A {
function spam() public pure {
// ...
}
function ham() public pure {
// ...
}
}
No:
pragma solidity >=0.4.0 <0.6.0;
contract A {
function spam() public pure {
// ...
}
function ham() public pure {
// ...
}
}
Maximum Line Length¶
PEP 8 recommendation に従い、行数は最大で79行(または99行)にとどめましょう。
これによりコードの可読性が向上します。
折り返し行は、次のガイドラインに従ってください。
- 最初の引数は左括弧に付けないでください
- 折り返し後のインデントは1つのみ使用してください
- 各引数は1行で表現できるようにします
- 最後の要素
);
は最終行に単独で置いてください
Function Calls
Yes:
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);
No:
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3);
Assignment Statements
Yes:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
argument1,
argument2,
argument3,
argument4
);
No:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,
argument2,
argument3,
argument4);
Event Definitions and Event Emitters
Yes:
event LongAndLotsOfArgs(
address sender,
address recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options
);
LongAndLotsOfArgs(
sender,
recipient,
publicKey,
amount,
options
);
No:
event LongAndLotsOfArgs(address sender,
address recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options);
LongAndLotsOfArgs(sender,
recipient,
publicKey,
amount,
options);
Source File Encoding¶
UTF-8とASCIIを使いましょう。
Imports¶
Import文は常にファイルの先頭に置きましょう。
Yes:
pragma solidity >=0.4.0 <0.6.0;
import "./Owned.sol";
contract A {
// ...
}
contract B is Owned {
// ...
}
No:
pragma solidity >=0.4.0 <0.6.0;
contract A {
// ...
}
import "./Owned.sol";
contract B is Owned {
// ...
}
Order of Functions¶
関数レベルの順序付けは、読者がどの関数を呼び出すことができるかを識別することを簡単にします。 これにより、コンストラクタとフォールバック定義を簡単に見つけるのに役立ちます。
関数は可視性(可視性修飾子を含む)に従って分類され、順序付けされるべきです:
- constructor
- fallback function(もしあるなら)
- external
- public
- internal
- private
グループ内では、関数の最後に view
や pure
を付けましょう。
Yes:
pragma solidity >=0.4.0 <0.6.0;
contract A {
constructor() public {
// ...
}
function() external {
// ...
}
// External functions
// ...
// External functions that are view
// ...
// External functions that are pure
// ...
// Public functions
// ...
// Internal functions
// ...
// Private functions
// ...
}
No:
pragma solidity >=0.4.0 <0.6.0;
contract A {
// External functions
// ...
function() external {
// ...
}
// Private functions
// ...
// Public functions
// ...
constructor() public {
// ...
}
// Internal functions
// ...
}
Whitespace in Expressions¶
次の場合は、余分なスペースを避けましょう:
単一行の関数宣言以外の、括弧、大括弧または大括弧のすぐ内側:
Yes:
spam(ham[1], Coin({name: "ham"}));
No:
spam( ham[ 1 ], Coin( { name: "ham" } ) );
Exception:
function singleLine() public { spam(); }
コンマ、セミコロンの直前:
Yes:
function spam(uint i, Coin coin) public;
No:
function spam(uint i , Coin coin) public ;
代入または他の演算子の周囲に、他のものと位置を合わせるためのスペースが複数ある場合:
Yes:
x = 1;
y = 2;
long_variable = 3;
No:
x = 1;
y = 2;
long_variable = 3;
フォールバック関数内にはホワイトスペースを含めないでください:
Yes:
function() external {
...
}
No:
function () external {
...
}
Control Structures¶
コントラクトボディ、ライブラリ、関数、および構造体を示す波括弧は、次のようになります。
- 宣言と同じ行で開きます
- 宣言の始めと同じインデントレベルで、それぞれの行を閉じます
- 波括弧を開くときは、シングルスペースに続きます
Yes:
pragma solidity >=0.4.0 <0.6.0;
contract Coin {
struct Bank {
address owner;
uint balance;
}
}
No:
pragma solidity >=0.4.0 <0.6.0;
contract Coin
{
struct Bank {
address owner;
uint balance;
}
}
これらのレコメンデーションは if
や else
、while
、for
といった制御構造にも適用されます。
さらに、制御構造 if
、while
、for
と、条件を表す括弧ブロックの間、
および条件括弧ブロックと括弧の間には、シングルスペースがあるべきです。
Yes:
if (...) {
...
}
for (...) {
...
}
No:
if (...)
{
...
}
while(...){
}
for (...) {
...;}
コントラクトボディに単一のステートメントが含まれる制御構造の場合、波括弧は省略しても構いません。 ただし、ステートメントが1行で表せられる場合に限ります。
Yes:
if (x < 10)
x += 1;
No:
if (x < 10)
someArray.push(Coin({
name: 'spam',
value: 42
}));
else
または else if
節を持つ if
ブロックの場合、else
は if
の閉じ括弧と同じ行に配置する必要があります。
これは他のブロックのような構造の規則と比較して例外です。
Yes:
if (x < 3) {
x += 1;
} else if (x > 7) {
x -= 1;
} else {
x = 5;
}
if (x < 3)
x += 1;
else
x -= 1;
No:
if (x < 3) {
x += 1;
}
else {
x -= 1;
}
Function Declaration¶
短い関数の宣言時は、関数内の左波括弧を関数宣言と同じ行に置くことを推奨します。
また、右波括弧は、関数宣言と同じインデントレベルになければなりません。
さらに、左波括弧の前には、単一のスペースを入れます。
Yes:
function increment(uint x) public pure returns (uint) {
return x + 1;
}
function increment(uint x) public pure onlyowner returns (uint) {
return x + 1;
}
No:
function increment(uint x) public pure returns (uint)
{
return x + 1;
}
function increment(uint x) public pure returns (uint){
return x + 1;
}
function increment(uint x) public pure returns (uint) {
return x + 1;
}
function increment(uint x) public pure returns (uint) {
return x + 1;}
コンストラクタを含むすべての関数の可視性に明示的にラベルを付ける必要があります。
Yes:
function explicitlyPublic(uint val) public {
doSomething();
}
No:
function implicitlyPublic(uint val) {
doSomething();
}
関数の可視性修飾子は、どのカスタム修飾子よりも先に来る必要があります。
Yes:
function kill() public onlyowner {
selfdestruct(owner);
}
No:
function kill() onlyowner public {
selfdestruct(owner);
}
長い関数の宣言時は、関数本体と同じインデントレベルで、各引数を独自の行に配置することを推奨します。 また、右括弧と左角括弧は、関数宣言と同じインデントレベルで、新しい行に単一に配置する必要があります。
Yes:
function thisFunctionHasLotsOfArguments(
address a,
address b,
address c,
address d,
address e,
address f
)
public
{
doSomething();
}
No:
function thisFunctionHasLotsOfArguments(address a, address b, address c,
address d, address e, address f) public {
doSomething();
}
function thisFunctionHasLotsOfArguments(address a,
address b,
address c,
address d,
address e,
address f) public {
doSomething();
}
function thisFunctionHasLotsOfArguments(
address a,
address b,
address c,
address d,
address e,
address f) public {
doSomething();
}
もしこの長い関数の宣言時に関数が修飾子を持つ場合、各修飾子は新しい行に単一に配置する必要があります。
Yes:
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyowner
priced
returns (address)
{
doSomething();
}
function thisFunctionNameIsReallyLong(
address x,
address y,
address z,
)
public
onlyowner
priced
returns (address)
{
doSomething();
}
No:
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyowner
priced
returns (address) {
doSomething();
}
function thisFunctionNameIsReallyLong(address x, address y, address z)
public onlyowner priced returns (address)
{
doSomething();
}
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyowner
priced
returns (address) {
doSomething();
}
複数のアウトプットパラメータとそのreturn文は Maximum Line Length セクションにある複数行をラップする場合と同じように行います。
Yes:
function thisFunctionNameIsReallyLong(
address a,
address b,
address c
)
public
returns (
address someAddressName,
uint256 LongArgument,
uint256 Argument
)
{
doSomething()
return (
veryLongReturnArg1,
veryLongReturnArg2,
veryLongReturnArg3
);
}
No:
function thisFunctionNameIsReallyLong(
address a,
address b,
address c
)
public
returns (address someAddressName,
uint256 LongArgument,
uint256 Argument)
{
doSomething()
return (veryLongReturnArg1,
veryLongReturnArg1,
veryLongReturnArg1);
}
引数が必要なコントラクトを継承するコンストラクタ関数において、関数宣言が長い場合や読みにくい場合は、 修飾子と同じ方法でベースコンストラクタを新しい行に配置することを推奨しています。
Yes:
pragma solidity >=0.4.0 <0.6.0;
// Base contracts just to make this compile
contract B {
constructor(uint) public {
}
}
contract C {
constructor(uint, uint) public {
}
}
contract D {
constructor(uint) public {
}
}
contract A is B, C, D {
uint x;
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
public
{
// do something with param5
x = param5;
}
}
No:
pragma solidity >=0.4.0 <0.6.0;
// Base contracts just to make this compile
contract B {
constructor(uint) public {
}
}
contract C {
constructor(uint, uint) public {
}
}
contract D {
constructor(uint) public {
}
}
contract A is B, C, D {
uint x;
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
public
{
x = param5;
}
}
contract X is B, C, D {
uint x;
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
public {
x = param5;
}
}
短い関数を1つのステートメントで宣言するときは、1行で宣言してもかまいません。
Permissible:
function shortFunction() public { doSomething(); }
関数宣言に関するこれらのガイドラインは、可読性を向上させることを目的としています。 このガイドは関数宣言時のすべてのケースを網羅しようとするものではないので、コードを書く場合はその都度最善の判断に従うべきでしょう。
Mappings¶
変数の宣言時には、キーワード mapping
をその型とスペースで区切らないでください。
また、ネストされた mapping
キーワードをそのタイプとスペースで区切らないでください。
Yes:
mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
No:
mapping (uint => uint) map;
mapping( address => bool ) registeredAddresses;
mapping (uint => mapping (bool => Data[])) public data;
mapping(uint => mapping (uint => s)) data;
Other Recommendations¶
- 文字列は、シングルクオーテーションではなくダブルクオーテーションで囲む必要があります:
Yes:
str = "foo";
str = "Hamlet says, 'To be or not to be...'";
No:
str = 'bar';
str = '"Be yourself; everyone else is already taken." -Oscar Wilde';
- 演算子はシングルスペースで囲ってください:
Yes:
x = 3;
x = 100 / 10;
x += 3 + 4;
x |= y && z;
No:
x=3;
x = 100/10;
x += 3+4;
x |= y&&z;
- 高い優先順位を持つ演算子は、優先順位を示すために周囲のスペースを除外することができます。 これは複雑な代入文の可読性を向上させることを目的としています。 また、演算子の両側には常に同じ量の空白を使用してください:
Yes:
x = 2**3 + 5;
x = 2*y + 3*z;
x = (a+b) * (a-b);
No:
x = 2** 3 + 5;
x = y+z;
x +=1;
Order of Layout¶
コントラクト内にある要素のレイアウトは次の順序に従ってください:
- Pragma statements
- Import statements
- Interfaces
- Libraries
- Contracts
各コントラクトやライブラリ、インターフェース内においては次の順序に従ってください:
- 型宣言
- 状態変数
- イベント
- 関数
注釈
イベントや状態変数での使用に近い型を宣言する方が明確な場合があります。
Naming Conventions¶
命名規則は、採用され広く使用されている場合に強力なものとなります。 異なる規則を使用すると、利用できない重要な meta 情報を伝えることができます。
ここで与えられた命名規則は可読性を改善することを目的としています。 そのため、それらは規則というより、むしろ名前を通してほとんどの情報を伝えられるという点に着目します。
最後に、コードベース内における一貫性は、この文書で説明されている規約よりも常に優先されるべきです。
Naming Styles¶
混乱を避けるために、以下の名前はさまざまな命名スタイルを指すために使用されます。
b
(単一の小文字)B
(単一の大文字)lowercase
lower_case_with_underscores
UPPERCASE
UPPER_CASE_WITH_UNDERSCORES
CapitalizedWords
(もしくはCapWords)mixedCase
(頭文字が小文字である点がCapitalizedWordsと異なります!)Capitalized_Words_With_Underscores
注釈
CapWordsでイニシャリズムを使用するときは、イニシャリズムのすべての文字を大文字にします。そのため、HTTPServerError は HttpServerError よりも良い命名といえます。イニシャルを使用する場合は、mixedCaseを使用します。名前の頭文字である場合は最初の1文字を小文字にする以外は、イニシャルのすべての文字を大文字にします。そのため、xmlHTTPRequest は XMLHTTPRequest よりも良い命名です。
Names to Avoid¶
l
- 小文字の elO
- 大文字の ohI
- 大文字の eye
1文字の変数名にこれらのいずれも使用しないでください。数字の1と0と区別がつかないケースがよくあります。
Contract and Library Names¶
- コントラクトとライブラリは、CapWordsスタイルを使用して命名する必要があります。例:
SimpleToken
、SmartBank
、CertificateHashRepository
、Player
、Congress
、Owned
など。 - コントラクトとライブラリの名前もそれらのファイル名と一致する必要があります。
- コントラクトファイルに複数のコントラクトやライブラリが含まれている場合、ファイル名は core contract と一致させる必要があります。ただしこの構造はお勧めできないため、でいるだけ回避しましょう。
以下の例に示すように、コントラクト名が Congress でライブラリ名が Owned の場合、それらに関連するファイル名は Congress.sol と ` Owned.sol` になります。
Yes:
pragma solidity >=0.4.0 <0.6.0;
// Owned.sol
contract Owned {
address public owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
}
// Congress.sol
import "./Owned.sol";
contract Congress is Owned, TokenRecipient {
//...
}
No:
pragma solidity >=0.4.0 <0.6.0;
// owned.sol
contract owned {
address public owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
}
// Congress.sol
import "./owned.sol";
contract Congress is owned, tokenRecipient {
//...
}
Struct Names¶
構造体はCapWordsスタイルを使用して命名する必要があります。例: MyCoin
、Position
、PositionXY
など。
Event Names¶
イベントはCapWordsスタイルを使って命名されるべきです。例: Deposit
、Transfer
、Approval
、BeforeTransfer
、AfterTransfer
など。
Function Names¶
コンストラクタ以外の関数はmixedCaseを使用するべきです。例: getBalance
、transfer
、verifyOwner
、addMember
、changeOwner
など。
Function Argument Names¶
関数の引数はmixedCaseを使用するべきです。例: initialSupply
、account
、recipientAddress
、senderAddress
、newOwner
など。
カスタム構造体を操作するライブラリ関数を書くときは、その構造体を最初の引数にして、常に self
という名前にします。
Local and State Variable Names¶
mixedCaseを使用してください。例: totalSupply
、remainingSupply
、balancesOf
、creatorAddress
、isPreSale
、tokenExchangeRate
など。
Constants¶
定数は単語を区切るアンダースコア付きのすべて大文字で名前を付ける必要があります。例: MAX_BLOCKS
、TOKEN_NAME
、TOKEN_TICKER
、CONTRACT_VERSION
など。
Modifier Names¶
mixedCaseを使用してください。例: onlyBy
、onlyAfter
、onlyDuringThePreSale
など。
Enums¶
列挙型は、単純な型宣言において、CapWordsスタイルを使用して命名する必要があります。例: TokenGroup
、Frame
、HashStyle
、CharacterLocation
など。
General Recommendations¶
TODO
NatSpec¶
Solidityコントラクトは、Ethereum Natural Language Specification Format のベースとなっている形式のコメントを付けることができます。
/// またはaで始まる1行または複数行の doxygen 表記に続く関数または規約の上にコメントを追加します。
例えば、コメントが追加された a simple smart contract のコントラクトは以下のようになります:
pragma solidity >=0.4.0 <0.6.0;
/// @author The Solidity Team
/// @title A simple storage example
contract SimpleStorage {
uint storedData;
/// Store `x`.
/// @param x the new value to store
/// @dev stores the number in the state variable `storedData`
function set(uint x) public {
storedData = x;
}
/// Return the stored value.
/// @dev retrieves the value of the state variable `storedData`
/// @return the stored value
function get() public view returns (uint) {
return storedData;
}
}
Natspecは特別な意味を持つdoxygenスタイルのタグを使います。
タグが使用されていない場合、コメントは @notice
として適用されます。
@notice
タグはNatSpecのメインタグで、読み手はソースコードを読んだことのないコントラクトユーザーを想定しています。
そのため、できるだけ内部の詳細についての推測させないようにするべきです。
また、すべてのタグはオプショナルです。
Tag | Description | Context |
---|---|---|
@title |
A title that describes the contract | contract, interface |
@author |
The name of the author | contract, interface, function |
@notice |
Explanation of functionality | contract, interface, function |
@dev |
Any extra details | contract, interface, function |
@param |
Parameter type followed by parameter name | function |
@return |
The return value of a contract's function | function |
Common Patterns¶
Withdrawal from Contracts¶
発行の後にファンドを送る方法としてお勧めなのはwithdrawalパターンを使うことです。直接 transfer
コールするのは直感的なEtherの送金方法ですが、潜在的にセキュリティリスクを実装することになるので、推奨しません。これに関しての詳しい情報は、Security Considerations を読んでください。
次の例はあるコントラクトにおける実際のwithdrawalパターンです。このコントラクトの目的は"richest"( King of the Ether に触発されました)になるためにたくさんのお金をコントラクトに送ることです。
下記のコントラクトでは、richestの座を奪われたら、新しくrichestになった人のファンドを受け取れます。
pragma solidity ^0.5.0;
contract WithdrawalContract {
address public richest;
uint public mostSent;
mapping (address => uint) pendingWithdrawals;
constructor() public payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];
// Remember to zero the pending refund before
// sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
次は逆にもっと直感的な送金パターンです。
pragma solidity ^0.5.0;
contract SendContract {
address payable public richest;
uint public mostSent;
constructor() public payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
// This line can cause problems (explained below).
richest.transfer(msg.value);
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
}
気づいて頂きたいのは、この例では攻撃者が、richest
に、失敗する(例えば、revert()
を使ったり、2300ガス以上を消費したりすることで)フォールバックファンクションを持ったコントラクトアドレスを入れることで、通常ではない状態をコントラクトに仕込めるということです。こういう方法によって、"毒された"コントラクトにファンドを送るために transfer
が呼ばれると常に失敗し、その結果、becomeRichest
が失敗し、コントラクトは永遠に動かなくなります。
一方、もし最初の例の"withdraw"パターンを使うと、攻撃者は自分のwithdrawの失敗しか起こせないので、他のコントラクトの機能は損なわれません。
Restricting Access¶
アクセス制限はコントラクトの共通パターンです。他の人やコンピューターがトランザクションやコントラクトのステートを読み取ることを制限することはできないということを覚えておいてください。暗号化で少し難しくはできますが、もしコントラクトがデータを読み込めるなら、他の人もできます。
他のコントラクト によるあなたのコントラクトのステートへの読み込みのアクセスは制限できます。状態変数を public
で宣言しなければ、これはデフォルトでそうなっています。
さらに、誰があなたのコントラクトのステートを変更したり、ファンクションを呼び出したりできるか制限することができます。そしてこれがこのセクションで説明することです。
function modifiers を使うことでこれらの制限をとても読みやすくします。
pragma solidity >=0.4.22 <0.6.0;
contract AccessRestriction {
// These will be assigned at the construction
// phase, where `msg.sender` is the account
// creating this contract.
address public owner = msg.sender;
uint public creationTime = now;
// Modifiers can be used to change
// the body of a function.
// If this modifier is used, it will
// prepend a check that only passes
// if the function is called from
// a certain address.
modifier onlyBy(address _account)
{
require(
msg.sender == _account,
"Sender not authorized."
);
// Do not forget the "_;"! It will
// be replaced by the actual function
// body when the modifier is used.
_;
}
/// Make `_newOwner` the new owner of this
/// contract.
function changeOwner(address _newOwner)
public
onlyBy(owner)
{
owner = _newOwner;
}
modifier onlyAfter(uint _time) {
require(
now >= _time,
"Function called too early."
);
_;
}
/// Erase ownership information.
/// May only be called 6 weeks after
/// the contract has been created.
function disown()
public
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)
{
delete owner;
}
// This modifier requires a certain
// fee being associated with a function call.
// If the caller sent too much, he or she is
// refunded, but only after the function body.
// This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`.
modifier costs(uint _amount) {
require(
msg.value >= _amount,
"Not enough Ether provided."
);
_;
if (msg.value > _amount)
msg.sender.transfer(msg.value - _amount);
}
function forceOwnerChange(address _newOwner)
public
payable
costs(200 ether)
{
owner = _newOwner;
// just some example condition
if (uint(owner) & 0 == 1)
// This did not refund for Solidity
// before version 0.4.0.
return;
// refund overpaid fees
}
}
どのファンクションコールへのアクセスが制限できるかというもっと特殊な方法は次の例で説明します。
State Machine¶
コントラクトはよくステートマシンとして振舞います。つまり、異なる挙動をする、もしくは異なるファンクションがコールされるいくつかの ステージ を持っています。ファンクションコールはしばしばステージを終わらせ、コントラクトを次のステージへ以降させます(特にコントラクトに 相互作用 があった場合)。いくつかのステージは自動的にあるポイントに そのうちに なっていることもよくあります。
この例としてブラインドオークションのコントラクトがあります。このコントラクトでは"ブラインドビッドの承認"ステージから始まり、"オークションの結果"で終わる"ビッドの公開"に移行します。
このシチュエーションで、ステートを作って、コントラクトの間違った使用法から守るため、ファンクションmodifierを使うことができます。
Example¶
次の例では、modifier atStage
はあるステージの時にのみファンクションが呼ばれるようにすることができます。
自動でのある時間での変遷はmodifier timeTransitions
でコントロールすることができ、全てのファンクションで使えます。
注釈
modifierの順番は重要です。 atStageがtimedTransitionsと一緒にする場合、timedTransitionsの後にatStageを付けてください。そうすることで新しいステージが考慮されます。
最後に、modifier transitionNext
はファンクションが終わった時に自動的に次のステージに行くために使用できます。
注釈
modifierはスキップされるかもしれません。 これはSolidityバージョン0.4.0より前にのみ適用されます。modifierはファンクションコールを使うのではなく、単純にコードを別の所に書くことで適用されているので、もしファンクションがreturnを使っていたら、transitionNext内のコードはスキップされるかもしれません。もしスキップさせたくないのであれば、ファンクション内マニュアルでnextStageを呼び出してください。 バージョン0.4.0からはファンクションが明示的にreturnしていても、modifierコードは実行されます。
pragma solidity >=0.4.22 <0.6.0;
contract StateMachine {
enum Stages {
AcceptingBlindedBids,
RevealBids,
AnotherStage,
AreWeDoneYet,
Finished
}
// This is the current stage.
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
modifier atStage(Stages _stage) {
require(
stage == _stage,
"Function cannot be called at this time."
);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
// Perform timed transitions. Be sure to mention
// this modifier first, otherwise the guards
// will not take the new stage into account.
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)
nextStage();
// The other stages transition by transaction
_;
}
// Order of the modifiers matters here!
function bid()
public
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{
// We will not implement that here
}
function reveal()
public
timedTransitions
atStage(Stages.RevealBids)
{
}
// This modifier goes to the next stage
// after the function is done.
modifier transitionNext()
{
_;
nextStage();
}
function g()
public
timedTransitions
atStage(Stages.AnotherStage)
transitionNext
{
}
function h()
public
timedTransitions
atStage(Stages.AreWeDoneYet)
transitionNext
{
}
function i()
public
timedTransitions
atStage(Stages.Finished)
{
}
}
List of Known Bugs¶
以下でSolidityコンパイラのセキュリティに関連したバグのリストがJSON形式で取得できます。 このファイル自身は Github repository 内に格納されています。 このリストはバージョン0.3.0まで遡って管理されますが、それより前のバージョンにのみ存在するバグについては記載されません。
bugs_by_version.json という別のファイルもあります。 これは、コンパイラの特定のバージョンに影響するバグをチェックするために使われます。
コントラクトソース検証ツールやコントラクトとやり取りするツールは、以下の基準に従いこのリストを参照する必要があります。
- リリースバージョンではなくnightlyビルドのコンパイラでコンパイルされたコントラクトは疑わしいです。 このリストはリリースされてないバージョンやnightlyビルドのバージョンは追随していないです。
- コントラクトが作成された時に、最新バージョンでコンパイルされていないコントラクトもまた、疑わしいです。 他のコントラクトから作成されたコントラクトには、creation chainを辿ってトランザクションまで戻り、creation dateとしてトランザクションの日付を使わなければいけません。
- 既知のバグが含まれたコンパイラでコンパイルされたコントラクトや、すでにリリースされた修正済みバグを含む新しいコンパイラで作られたコントラクトは非常に疑わしいです。
既知のバグのJSONファイルはオブジェクトの配列になっており、それぞれのバグが以下のキーを持つ1つのオブジェクトです。
- name
- バグに振られるユニークな名前
- summary
- バグの短い説明
- description
- バグの詳細な説明
- link
- より詳細な説明があるサイトのURL(任意)
- introduced
- バグを含む最初に公開されたコンパイラのバージョン(任意)
- fixed
- バグを含まない最初に公開されたコンパイラのバージョン(任意)
- publish
- バグが公けに知られた日付(任意)
- severity
- バグの緊急度: very low, low, medium, high コントラクトのテストでの発見可能性、発生頻度、悪用時に潜在被害を考慮してください。
- conditions
- バグの発生条件。現在、booleanの
optimizer
を含められるオブジェクトです。 これはオプティマイザがバグを有効にするために切り替えられなければならない、という意味です。 conditionsが記載されていない場合、バグが存在するとみなされます。 - check
- このフィールドは、スマートコントラクトがバグを含むかそうでないかを報告する様々なチェックを含みます。 最初のチェックタイプはJavascriptの正規表現です。これは、バグが存在するときにソースコード("source-regex")にたいして照合されるものです。 もし一致するものがなければ、このバグはほとんど発生しない可能性があります。 より正確にするため、チェックはコメントを取り除いた後のソースコードに適用されるべきです。 2番目のチェックタイプは、Solidityプログラム ("ast-compact-json-path") のcompact ASTにチェックされるパターンです。 検索クエリは、JsonPath 式です。 もし、少なくともthe Solidity ASTのパスが1つでもクエリに一致した場合、このバグはおそらく存在します。
[
{
"name": "ExpExponentCleanup",
"summary": "Using the ** operator with an exponent of type shorter than 256 bits can result in unexpected values.",
"description": "Higher order bits in the exponent are not properly cleaned before the EXP opcode is applied if the type of the exponent expression is smaller than 256 bits and not smaller than the type of the base. In that case, the result might be larger than expected if the exponent is assumed to lie within the value range of the type. Literal numbers as exponents are unaffected as are exponents or bases of type uint256.",
"fixed": "0.4.25",
"severity": "medium/high",
"check": {"regex-source": "[^/]\\*\\* *[^/0-9 ]"}
},
{
"name": "EventStructWrongData",
"summary": "Using structs in events logged wrong data.",
"description": "If a struct is used in an event, the address of the struct is logged instead of the actual data.",
"introduced": "0.4.17",
"fixed": "0.4.25",
"severity": "very low",
"check": {"ast-compact-json-path": "$..[?(@.nodeType === 'EventDefinition')]..[?(@.nodeType === 'UserDefinedTypeName' && @.typeDescriptions.typeString.startsWith('struct'))]"}
},
{
"name": "NestedArrayFunctionCallDecoder",
"summary": "Calling functions that return multi-dimensional fixed-size arrays can result in memory corruption.",
"description": "If Solidity code calls a function that returns a multi-dimensional fixed-size array, array elements are incorrectly interpreted as memory pointers and thus can cause memory corruption if the return values are accessed. Calling functions with multi-dimensional fixed-size arrays is unaffected as is returning fixed-size arrays from function calls. The regular expression only checks if such functions are present, not if they are called, which is required for the contract to be affected.",
"introduced": "0.1.4",
"fixed": "0.4.22",
"severity": "medium",
"check": {"regex-source": "returns[^;{]*\\[\\s*[^\\] \\t\\r\\n\\v\\f][^\\]]*\\]\\s*\\[\\s*[^\\] \\t\\r\\n\\v\\f][^\\]]*\\][^{;]*[;{]"}
},
{
"name": "OneOfTwoConstructorsSkipped",
"summary": "If a contract has both a new-style constructor (using the constructor keyword) and an old-style constructor (a function with the same name as the contract) at the same time, one of them will be ignored.",
"description": "If a contract has both a new-style constructor (using the constructor keyword) and an old-style constructor (a function with the same name as the contract) at the same time, one of them will be ignored. There will be a compiler warning about the old-style constructor, so contracts only using new-style constructors are fine.",
"introduced": "0.4.22",
"fixed": "0.4.23",
"severity": "very low"
},
{
"name": "ZeroFunctionSelector",
"summary": "It is possible to craft the name of a function such that it is executed instead of the fallback function in very specific circumstances.",
"description": "If a function has a selector consisting only of zeros, is payable and part of a contract that does not have a fallback function and at most five external functions in total, this function is called instead of the fallback function if Ether is sent to the contract without data.",
"fixed": "0.4.18",
"severity": "very low"
},
{
"name": "DelegateCallReturnValue",
"summary": "The low-level .delegatecall() does not return the execution outcome, but converts the value returned by the functioned called to a boolean instead.",
"description": "The return value of the low-level .delegatecall() function is taken from a position in memory, where the call data or the return data resides. This value is interpreted as a boolean and put onto the stack. This means if the called function returns at least 32 zero bytes, .delegatecall() returns false even if the call was successful.",
"introduced": "0.3.0",
"fixed": "0.4.15",
"severity": "low"
},
{
"name": "ECRecoverMalformedInput",
"summary": "The ecrecover() builtin can return garbage for malformed input.",
"description": "The ecrecover precompile does not properly signal failure for malformed input (especially in the 'v' argument) and thus the Solidity function can return data that was previously present in the return area in memory.",
"fixed": "0.4.14",
"severity": "medium"
},
{
"name": "SkipEmptyStringLiteral",
"summary": "If \"\" is used in a function call, the following function arguments will not be correctly passed to the function.",
"description": "If the empty string literal \"\" is used as an argument in a function call, it is skipped by the encoder. This has the effect that the encoding of all arguments following this is shifted left by 32 bytes and thus the function call data is corrupted.",
"fixed": "0.4.12",
"severity": "low"
},
{
"name": "ConstantOptimizerSubtraction",
"summary": "In some situations, the optimizer replaces certain numbers in the code with routines that compute different numbers.",
"description": "The optimizer tries to represent any number in the bytecode by routines that compute them with less gas. For some special numbers, an incorrect routine is generated. This could allow an attacker to e.g. trick victims about a specific amount of ether, or function calls to call different functions (or none at all).",
"link": "https://blog.ethereum.org/2017/05/03/solidity-optimizer-bug/",
"fixed": "0.4.11",
"severity": "low",
"conditions": {
"optimizer": true
}
},
{
"name": "IdentityPrecompileReturnIgnored",
"summary": "Failure of the identity precompile was ignored.",
"description": "Calls to the identity contract, which is used for copying memory, ignored its return value. On the public chain, calls to the identity precompile can be made in a way that they never fail, but this might be different on private chains.",
"severity": "low",
"fixed": "0.4.7"
},
{
"name": "OptimizerStateKnowledgeNotResetForJumpdest",
"summary": "The optimizer did not properly reset its internal state at jump destinations, which could lead to data corruption.",
"description": "The optimizer performs symbolic execution at certain stages. At jump destinations, multiple code paths join and thus it has to compute a common state from the incoming edges. Computing this common state was simplified to just use the empty state, but this implementation was not done properly. This bug can cause data corruption.",
"severity": "medium",
"introduced": "0.4.5",
"fixed": "0.4.6",
"conditions": {
"optimizer": true
}
},
{
"name": "HighOrderByteCleanStorage",
"summary": "For short types, the high order bytes were not cleaned properly and could overwrite existing data.",
"description": "Types shorter than 32 bytes are packed together into the same 32 byte storage slot, but storage writes always write 32 bytes. For some types, the higher order bytes were not cleaned properly, which made it sometimes possible to overwrite a variable in storage when writing to another one.",
"link": "https://blog.ethereum.org/2016/11/01/security-alert-solidity-variables-can-overwritten-storage/",
"severity": "high",
"introduced": "0.1.6",
"fixed": "0.4.4"
},
{
"name": "OptimizerStaleKnowledgeAboutSHA3",
"summary": "The optimizer did not properly reset its knowledge about SHA3 operations resulting in some hashes (also used for storage variable positions) not being calculated correctly.",
"description": "The optimizer performs symbolic execution in order to save re-evaluating expressions whose value is already known. This knowledge was not properly reset across control flow paths and thus the optimizer sometimes thought that the result of a SHA3 operation is already present on the stack. This could result in data corruption by accessing the wrong storage slot.",
"severity": "medium",
"fixed": "0.4.3",
"conditions": {
"optimizer": true
}
},
{
"name": "LibrariesNotCallableFromPayableFunctions",
"summary": "Library functions threw an exception when called from a call that received Ether.",
"description": "Library functions are protected against sending them Ether through a call. Since the DELEGATECALL opcode forwards the information about how much Ether was sent with a call, the library function incorrectly assumed that Ether was sent to the library and threw an exception.",
"severity": "low",
"introduced": "0.4.0",
"fixed": "0.4.2"
},
{
"name": "SendFailsForZeroEther",
"summary": "The send function did not provide enough gas to the recipient if no Ether was sent with it.",
"description": "The recipient of an Ether transfer automatically receives a certain amount of gas from the EVM to handle the transfer. In the case of a zero-transfer, this gas is not provided which causes the recipient to throw an exception.",
"severity": "low",
"fixed": "0.4.0"
},
{
"name": "DynamicAllocationInfiniteLoop",
"summary": "Dynamic allocation of an empty memory array caused an infinite loop and thus an exception.",
"description": "Memory arrays can be created provided a length. If this length is zero, code was generated that did not terminate and thus consumed all gas.",
"severity": "low",
"fixed": "0.3.6"
},
{
"name": "OptimizerClearStateOnCodePathJoin",
"summary": "The optimizer did not properly reset its internal state at jump destinations, which could lead to data corruption.",
"description": "The optimizer performs symbolic execution at certain stages. At jump destinations, multiple code paths join and thus it has to compute a common state from the incoming edges. Computing this common state was not done correctly. This bug can cause data corruption, but it is probably quite hard to use for targeted attacks.",
"severity": "low",
"fixed": "0.3.6",
"conditions": {
"optimizer": true
}
},
{
"name": "CleanBytesHigherOrderBits",
"summary": "The higher order bits of short bytesNN types were not cleaned before comparison.",
"description": "Two variables of type bytesNN were considered different if their higher order bits, which are not part of the actual value, were different. An attacker might use this to reach seemingly unreachable code paths by providing incorrectly formatted input data.",
"severity": "medium/high",
"fixed": "0.3.3"
},
{
"name": "ArrayAccessCleanHigherOrderBits",
"summary": "Access to array elements for arrays of types with less than 32 bytes did not correctly clean the higher order bits, causing corruption in other array elements.",
"description": "Multiple elements of an array of values that are shorter than 17 bytes are packed into the same storage slot. Writing to a single element of such an array did not properly clean the higher order bytes and thus could lead to data corruption.",
"severity": "medium/high",
"fixed": "0.3.1"
},
{
"name": "AncientCompiler",
"summary": "This compiler version is ancient and might contain several undocumented or undiscovered bugs.",
"description": "The list of bugs is only kept for compiler versions starting from 0.3.0, so older versions might contain undocumented bugs.",
"severity": "high",
"fixed": "0.3.0"
}
]
Contributing¶
貢献はいつでも歓迎します
始めるには、まず、Solidityのコンポーネントとビルドプロセスを理解するため、Building from Source を確認してください。 また、Solidityでスマートコントラクトを書くことに精通することも役立つでしょう。
以下の領域が特に手助けが必要です。
- ドキュメントを改善する
- StackExchange や Solidity Gitter に投稿されたユーザーからの質問に答える
- Solidity's GitHub issues に投稿されたissueに回答し修正する。特にこのタグがついた good first issue ものは外部貢献者のための入門的なissueになります。
このプロジェクト(issues, pull requests, Gitter channels含む) は Contributor Code of Conduct でリリースされていることに注意してください。 あなたはこの規約に同意することになります。
How to Report Issues¶
issueの報告には、 GitHub issues tracker を使って、以下の項目を含めるようにしてください。
- どのSolidityバージョンをつかっているのか
- どんなソースコードだったのか(可能であれば)
- どのプラットフォームで実行していたか
- 再現方法
- issueの結果はどのようなものか
- 期待する動作はどのようなものか
issueを再現するために最低限のソースコードにするのは有用で、誤解を明らかにします。
Workflow for Pull Requests¶
貢献するために、develop
ブランチをフォークし、あなたの変更をそこにいれてください。
コミットメッセージには、あなたの変更(小さな変更でなければ)の what に加えて why を詳細にいれるべきです。
もしあなたが変更を加えた後に develop
からpullする必要がある場合(例えばコンフリクトのマージする)、
私たちがあなたの変更のレビューをするのを簡単にするため、
git merge
を使うことは避けて、git rebase
を使ってください。
また、追加の機能を実装している場合、適切なテストケースを test/
に追加してください。(下記参照)
しかし、もし大きな変更をする場合、まず、 Solidity Development Gitter channel に相談してください。 (上記で参照したものとは異なり、こちらはコンパイラと言語開発用です)
新機能とバグ修正は Changelog.md
に追加されるべきです。
前回のエントリーのスタイルにしたがってください。
最後に、このプロジェクトでは、coding style を尊重してください。 私たちはCIテストを実行してはいますが、pull requestsを送る前にはローカルでテストしビルドするようにしてください。
あなたの貢献に感謝いたします。
Running the compiler tests¶
./scripts/tests.sh
スクリプトはほとんどのSolidityテストを実行し、aleth
を走らせます。
aleth
がパスに見つかる場合は実行しますが、ダウンロードは行いませんので、最初にインストールしておく必要があります。
詳細を確認してください。
Solidityは様々なテストを含んでおり、ほとんどが soltest
アプリケーションに同包されています。
それらのいくつかはテストモードで aleth
クライアントを必要とし、libz3
を必要とするものもあります。
aleth
も libz3
も必要としない基本的なテストセットを実行するには、
./scripts/soltest.sh --no-ipc --no-smt
を実行します。
このスクリプトは ./build/test/soltest
を内部的に走らせます。
注釈
Windows環境のこれらの作業で、上記の基本的なテストセットをGit Bash上でalethやlibz3なしで実行したい場合、
./build/test/Release/soltest.exe -- --no-ipc --no-smt
このコマンドを実行する必要があるでしょう。もし標準のコマンドプロンプトで実行する場合は、.\build\test\Release\soltest.exe -- --no-ipc --no-smt
を実行します。
--no-smt
オプションは libz3
を必要とするテストを無効にし、--no-ipc
オプションは aleth
を必要とするテストを無効にします。
ipcテスト(生成されたコードのセマンティックをテストするもの)を実行したい場合、
aleth
をインストールします。
その後、テストモードで実行します。
aleth --db memorydb --test -d /tmp/testeth
実際のテストを実行するには、./scripts/soltest.sh --ipcpath /tmp/testeth/geth.ipc
を使います。
テストの一部を実行する場合は、フィルタを使います。
./scripts/soltest.sh -t TestSuite/TestName --ipcpath /tmp/testeth/geth.ipc
TestName
はワイルドカードを指定できます *
。
例えば、実行したテストがあるとして、
./scripts/soltest.sh -t "yulOptimizerTests/disambiguator/*" --no-ipc --no-smt
を実行すると、disambiguatorの全てのテストが実行されます。
全てテストを確認するには、
./build/test/soltest --list_content=HRF -- --ipcpath /tmp/irrelevant
を使います。
もしGDBでデバッグしたい場合は、通常とは異なる方法でビルドしていることが必要です。
以下のコマンドを build
フォルダで実行します。
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
これは、あなたが --debug
フラグを使いテストをデバッグする際のシンボルを生成します。
これで、関数や変数をbreakしたりprintで表示したりすることができるようになります。
./scripts/tests.sh
スクリプトも、soltest
で見つかったテストに加えて、コマンドラインテストを実行しテストをコンパイルします。
CIはEmscriptenのコンパイルを必要とする追加のテスト( solc-js
やサードパーティのSolidityフレームワークのテストを含みます)を実行します。
注釈
いくつかの aleth
のバージョンはテストに使うことはできません。
SolidityのCIで使っているものと同じバージョンを使うことを推奨します。
現在CIはバージョン aleth
の 1.5.0-alpha.7
を使っています。
Writing and running syntax tests¶
構文テストは、コンパイラが無効なコードに正しいエラーメッセージを生成し、適切に有効なコードを受け入れることをチェックします。
それらは tests/libsolidity/syntaxTests
フォルダ内へ個別のファイルに格納されます。
それらのファイルは、個別のテストケースの正しい結果とアノテーションを含んでいます。
テストスイートは、正しい結果に対してチェックしコンパイルします。
例えば、./test/libsolidity/syntaxTests/double_stateVariable_declaration.sol
では、
contract test {
uint256 variable;
uint128 variable;
}
// ----
// DeclarationError: (36-52): Identifier already declared.
構文テストは、少なくともセパレータ // ----
に続くテスト自身のコントラクトを含まなければいけません。
セパレータに続くコメントは、正しいコンパイラエラーやワーニングを記述するのに使われます。
数字の範囲は、エラーが発生したソースコードの場所を指定しています。
コントラクトにエラーやワーニングなしでコンパイルしたい場合、セパレータとコメントを削除することができます。
上記の例だと、variable
変数は2度宣言されてます。
これは、すでに宣言されていますという識別子の DeclarationError
となります。
isoltest
というツールはこれらのテストに使え、./build/test/tools/
にあります。
このツールは、対話的に、お好みのエディタを使って、失敗しているコントラクトを編集することができます。
variable
を削除して、このテストを突破してみましょう!
contract test {
uint256 variable;
}
// ----
// DeclarationError: (36-52): Identifier already declared.
再度 ./build/test/isoltest
を実行すると失敗します。
syntaxTests/double_stateVariable_declaration.sol: FAIL
Contract:
contract test {
uint256 variable;
}
Expected result:
DeclarationError: (36-52): Identifier already declared.
Obtained result:
Success
isoltest
は得られた結果の次に期待する結果を表示します。また、現在のコントラクトの編集や更新、スキップする方法や、アプリケーションを終了する方法など方法も提供します。
失敗してるテストのためにいくつかの選択肢を提供します。
edit
:isoltest
はエディタでコントラクトを開こうとし、修正できるます。isoltest --editor /path/to/editor
としてコマンドラインで与えられたエディタか、EDITOR
環境変数で設定されいてるものか/usr/bin/editor
を使います。(この順番で設定されます).update
: テストのコントラクトの期待値を更新します。満たされていない期待値を取り除き、不足している期待値を追加することアノテーションを更新します。このテストは再度実行されます。skip
: 特定のテストの実行をスキップします。quit
:isoltest
を終了します。
全てのテスト実行を終了する quit
をのぞいて全てのオプションは現在のコントラクトへ適用します。
自動的、上記のテストは以下に変更され、
contract test {
uint256 variable;
}
// ----
再実行され、テストはパスします。
Re-running test case...
syntaxTests/double_stateVariable_declaration.sol: OK
注釈
そのテストを説明するコントラクトファイルの名前を選んでみてください。例えば、double_variable_declaration.sol
などです。
継承のテストやクロスコントラクトのテスト以外で、1つのファイルに複数のコントラクトを配置していはいけません。
各ファイルは新しい機能の側面のみテストすべきです。
Running the Fuzzer via AFL¶
Fuzzingは例外的な状態(segmentation faults, exceptions, etc)を見つけるための、ランダムインプットでプログラムを実行させるテクニックです。
現代のfuzzersはかしこく、インプットの中で有向探索を実行します。
インプットとして、ソースコードをとる solfuzzer
と呼ばれる特別なバイナリがあります。
内部コンパイラエラー、やsegmentation faultや類似のエラーが発生した時は失敗しますが、
コードにエラーが含まれていても失敗しません。こうして、fuzzingツールはコンパイラの中の内部的な問題を見つけることができます。
私たちは主に、AFL <http://lcamtuf.coredump.cx/afl/> をfuzzingに使います。リポジトリ(afl, afl-clang)からAFLパッケージをダウンロードしインストールか、手動でビルドする必要があります。
次に、自分用のコンパイラとしてAFLとSolidityをビルドします(もしくは、単に solfuzzer
バイナリを使います)。
cd build
# if needed
make clean
cmake .. -DCMAKE_C_COMPILER=path/to/afl-gcc -DCMAKE_CXX_COMPILER=path/to/afl-g++
make solfuzzer
この段階では以下のようなメッセージを見ることになるでしょう。
Scanning dependencies of target solfuzzer
[ 98%] Building CXX object test/tools/CMakeFiles/solfuzzer.dir/fuzzer.cpp.o
afl-cc 2.52b by <lcamtuf@google.com>
afl-as 2.52b by <lcamtuf@google.com>
[+] Instrumented 1949 locations (64-bit, non-hardened mode, ratio 100%).
[100%] Linking CXX executable solfuzzer
このようなメッセージ表示されない場合、cmakeフラグをAFLのclangバイナリに向けるようにしてみてください。
# if previously failed
make clean
cmake .. -DCMAKE_C_COMPILER=path/to/afl-clang -DCMAKE_CXX_COMPILER=path/to/afl-clang++
make solfuzzer
そうしなければ、直ちにバイナリはインスツルメントされてないというエラーと共にfuzzerは停止します。
afl-fuzz 2.52b by <lcamtuf@google.com>
... (truncated messages)
[*] Validating target binary...
[-] Looks like the target binary is not instrumented! The fuzzer depends on
compile-time instrumentation to isolate interesting test cases while
mutating the input data. For more information, and for tips on how to
instrument binaries, please see /usr/share/doc/afl-doc/docs/README.
When source code is not available, you may be able to leverage QEMU
mode support. Consult the README for tips on how to enable this.
(It is also possible to use afl-fuzz as a traditional, "dumb" fuzzer.
For that, you can use the -n option - but expect much worse results.)
[-] PROGRAM ABORT : No instrumentation detected
Location : check_binary(), afl-fuzz.c:6920
次に、サンプルソースファイルで、それはfuzzerに簡単にエラーを見つけられるようする実例のソースコードが必要です。 あなたは、文法テストからファイルをコピーするか、ドキュメントや他のテストからテストファイルを抽出することができます。
mkdir /tmp/test_cases
cd /tmp/test_cases
# extract from tests:
path/to/solidity/scripts/isolate_tests.py path/to/solidity/test/libsolidity/SolidityEndToEndTest.cpp
# extract from documentation:
path/to/solidity/scripts/isolate_tests.py path/to/solidity/docs docs
AFLドキュメントはコーパス(初期のインプットファイル)が大きすぎないようにしています。これらのファイル自身は1 kBを超えず、機能あたり多くても1ファイルであるべきです。
afl-cmin
というインプットファイルをバイナリと似た動作に整えるツールもあります。
fuzzerを実行してください( -m
はメモリサイズを60 MBへ拡張します):
afl-fuzz -m 60 -i /tmp/test_cases -o /tmp/fuzzer_reports -- /path/to/solfuzzer
fuzzerは /tmp/fuzzer_reports
に失敗するソースファイルを生成します。
同じエラーを発生させる似たソースファイルがあることもありますが、scripts/uniqueErrors.sh
を使うとユニークなエラーのみ絞り込むことができます。
Whiskers¶
Whiskers は Mustache <https://mustache.github.io> と似た文字型テンプレートシステムです。 コードの保守性や検証可能性、可読性のため、様々な場所でコンパイラが使います。
シンタックスはMustacheと大きく異なり、{{
と }}
は <
と >
に置き換わっています。
それはパースしやすくするのと、Inline Assembly とコンフリクトするのを避けるためです(インラインアセンブラでは <
と >
は不正ですが、{
と }
はブロックを区切るのに使います)
リストは1階層のみ処理され再帰しない、という制限がありますが、将来は変更されるでしょう。
A rough specification is the following: 大まかな仕様として以下があります。
<name>
は全て文字型の name
にエスケープや繰り返しの置換することなしに置き換われます。
領域は <#name>...</name>
で区切ることができます。テンプレートシステムへ与えられた変数のセットとして、内容を連結したものに置きかわりますし、
<inner>
は毎回、それぞれの値で置き換わります。
トップレベルの変数はその領域内部でも使うことができます。
LLL¶
LLLはS式構文に対応したEVMのための低水準言語です。
SolidityリポジトリはLLLコンパイラを含んでおり、Solidityとアセンブラサブシステムを共有します。 しかし、コンパイルされていることを除けば、これ以上改善させることはありません。
以下のように特別に指定しない限り、ビルドに含まれることはありません。
$ cmake -DLLL=ON ..
$ cmake --build .
警告
LLLのコードは非推奨扱いであり、将来Solidityリポジトリから削除されるでしょう。