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 を参照ください。
balanceandtransfer
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,delegatecallandstaticcall
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
xis of typebytesI, thenx[k]for0 <= k < Ireturns thekth 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 になります。