Solidityは静的型付け言語です。 つまり、各変数(ステートとローカル)の型を指定する必要があります。 Solidityにはいくつかの基本的な型があり、それらを組み合わせて複雑な型を作ることができます。

また、演算子を含む式では、型同士が相互に作用できます。 様々な演算子のクイックリファレンスについては、 演算子の優先順位 を参照してください。

Solidityには "undefined"や"null"の値の概念はありませんが、新しく宣言された変数は常にその型に依存した デフォルト値 を持ちます。 予期せぬ値を処理するには、 リバート を使ってトランザクション全体をリバートするか、成功を示す bool 値を2番目に持つタプルを返す必要があります。

値型

下記で紹介するものは、変数が常に値で渡されるため、値型と呼ばれます。 つまり、関数の引数や代入で使われるときは、常にコピーされます。

ブーリアン

bool: 可能な値は定数 truefalse です。

演算子:

  • ! (論理的否定)

  • && (論理積、"and")

  • || (論理和、"or")

  • == (等号)

  • != (不等号)

演算子 ||&& は、共通の短絡ルールを適用します。 つまり、式 f(x) || g(y) において、 f(x)true と評価された場合、 g(y) はたとえ副作用があったとしても評価されません。

整数

int / uint: さまざまなサイズの符号付きおよび符号なし整数。 キーワード uint8uint256 (8~256ビットの符号なし)と int8int2568 の間隔で。 uintint は、それぞれ uint256int256 のエイリアス。

演算子:

  • 比較: <=, <, ==, !=, >=, > ( bool に評価)

  • ビット演算子: &, |, ^ (ビットごとの排他的論理和), ~ (ビットごとの否定)

  • シフト演算子: << (左シフト), >> (右シフト)

  • 算術演算子: +, -, 単項 - (符号付き整数の場合のみ), *, /, % (モジュロ), ** (指数)

整数型の X の場合、 type(X).mintype(X).max を使って、その型で表現できる最小値と最大値にアクセスできます。

警告

Solidityの整数は、ある範囲に制限されています。 例えば、 uint32 の場合、 0 から 2**32 - 1 までとなります。 これらの型に対して算術演算を行うには2つのモードがあります。 "ラッピング"または"チェックなし"モードと"チェックあり"モードです。 デフォルトでは、演算は常に"チェック"されます。 つまり、演算結果が型の値の範囲外になると、呼び出しは failing assertion でリバートされます。 unchecked { ... } を使って"チェックなし"モードに切り替えることができます。 詳細は unchecked の項を参照してください。

比較

比較の値は、整数値を比較して得られる値です。

ビット演算

ビット演算は、数値の2の補数表現に対して行われます。 これは例えば、 ~int256(0) == int256(-1) だということです。

シフト

シフト演算の結果は、左オペランドの型を持ち、型に合わせて結果を切り捨てます。 右のオペランドは符号なしの型でなければならず、符号ありの型でシフトしようとするとコンパイルエラーになります。

シフトは、以下の方法で2の累乗を使って"シミュレート"できます。 なお、左オペランドの型への切り捨ては常に最後に行われますが、明示的には言及されていません。

  • x << y は数学的表現で x * 2**y に相当します。

  • x >> y は、数学的表現で x / 2**y を負の無限大に向けて丸めたものに相当します。

警告

バージョン 0.5.0 以前では、負の x の右シフト x >> y は、ゼロに向かって丸められた数学的表現 x / 2**y に相当していました。 つまり、右シフトでは、(負の無限大に向かって)切り捨てるのではなく、(ゼロに向かって)切り上げられていたのです。

注釈

シフト演算では、算術演算のようなオーバーフローチェックが行われません。 その代わり、結果は常に切り捨てられます。

加算、減算、乗算

加算、減算、乗算には通常のセマンティクスがあり、オーバーフローとアンダーフローに関しては2つの異なるモードがあります。

デフォルトでは、すべての演算はアンダーフローまたはオーバーフローをチェックしますが、 unchecked block を使ってこれを無効にでき、結果としてラッピング演算が行われます。 詳細はこのセクションを参照してください。

-x という表現は、 Tx の型として (T(0) - x) と同等です。 これは、符号付きの型にのみ適用できます。 x が負であれば、 -x の値は正になります。 また、2の補数表現にはもう1つの注意点があります。

int x = type(int).min; の場合は、 -x は正の範囲に当てはまりません。 つまり、 unchecked { assert(-x == x); } は動作し、 -x という表現をcheckedモードで使用すると、アサーションが失敗するということになります。

除算

演算の結果の型は常にオペランドの1つの型であるため、整数の除算は常に整数になります。 Solidityでは、除算はゼロに向かって丸められます。 これは、 int256(-5) / int256(2) == int256(-2) を意味します。

これに対し、 リテラル での除算では、任意の精度の分数値が得られることに注意してください。

注釈

ゼロによる除算は、 パニックエラー を引き起こします。 このチェックは unchecked { ... } で無効にできます。

注釈

type(int).min / (-1) という式は、除算でオーバーフローが発生する唯一のケースです。 チェックされた算術モードでは、これは失敗したアサーションを引き起こしますが、ラッピングモードでは、値は type(int).min になります。

モジュロ

モジュロ演算 a % n では、オペランド a をオペランド n で除算した後の余り r が得られますが、ここでは q = int(a / n)r = a - (n * q) が使われています。 つまり、モジュロの結果は左のオペランドと同じ符号(またはゼロ)になり、 a % n == -(-a % n) は負の a の場合も同様です。

  • int256(5) % int256(2) == int256(1)

  • int256(5) % int256(-2) == int256(1)

  • int256(-5) % int256(2) == int256(-1)

  • int256(-5) % int256(-2) == int256(-1)

注釈

ゼロでのモジュロは パニックエラー を引き起こします。 このチェックは unchecked { ... } で無効にできます。

指数

指数計算は、指数が符号なしの型の場合のみ可能です。 指数計算の結果の型は、常に基底の型と同じです。 結果を保持するのに十分な大きさであることに注意し、潜在的なアサーションの失敗やラッピングの動作に備えてください。

注釈

チェックされたモードでは、指数計算は小さな基底に対して比較的安価な exp というオペコードしか使いません。 x**3 の場合には x*x*x という表現の方が安いかもしれません。 いずれにしても、ガスコストのテストとオプティマイザの使用が望まれます。

注釈

なお、 0**0 はEVMでは 1 と定義されています。

固定小数点

警告

固定小数点数はSolidityではまだ完全にはサポートされていません。 宣言できますが、代入したり、代入解除したりできません。

fixed / ufixed: さまざまなサイズの符号付きおよび符号なしの固定小数点数。 キーワード ufixedMxNfixedMxNM は型で取るビット数、 N は小数点以下の数を表します。 M は8で割り切れるものでなければならず、8から256ビットまであります。 N は0から80までの値でなければなりません。 ufixedfixed は、それぞれ ufixed128x18fixed128x18 のエイリアスです。

演算子:

  • 比較: <=, <, ==, !=, >=, > ( bool に評価)

  • 算術演算子: +, -, 単項 -, *, /, % (モジュロ)

注釈

浮動小数点(多くの言語では floatdouble 、正確にはIEEE754の数値)と固定小数点の主な違いは、整数部と小数部(小数点以下の部分)に使用するビット数が、前者では柔軟に設定できるのに対し、後者では厳密に定義されていることです。 一般に、浮動小数点では、ほぼすべての空間を使って数値を表現するが、小数点の位置を決めるのは少数のビットです。

アドレス

アドレス型には2つの種類がありますが、ほとんど同じです。

  • address: 20バイトの値(Ethereumのアドレスのサイズ)を保持します。

  • address payable: address と同じですが、メンバの transfersend が追加されます。

この区別の背景にある考え方は、 address payable はEtherを送ることができるアドレスであるのに対し、プレーンな address はEtherを送ることが想定されない、というものです。 例えば、 address の変数に格納されたアドレスがEtherを受信できるように構築されていないスマートコントラクトである可能性があります。

型変換:

address payable から address への暗黙の変換は許されますが、 address から address payable への変換は payable(<address>) を介して明示的に行う必要があります。

uint160 、整数リテラル、 bytes20 、コントラクト型については、 address との明示的な変換が可能です。

address 型とコントラクト型の式のみが、明示的な変換 payable(...) によって address payable 型に変換できます。 コントラクト型については、コントラクトがEtherを受信できる場合、つまりコントラクトが receive またはpayableのフォールバック関数を持っている場合にのみ、この変換が可能です。 payable(0) は有効であり、このルールの例外であることに注意してください。

注釈

address 型の変数が必要で、その変数にEtherを送ろうと思っているなら、その変数の型を address payable と宣言して、この要求を見えるようにします。 また、この区別や変換はできるだけ早い段階で行うようにしてください。 address``と ``address payable``の区別はバージョン0.5.0で導入されました。 また、そのバージョンからコントラクトは暗黙的に ``address 型に変換できませんが、receiveまたはpayable fallback関数があれば、明示的に address または address payable 型に変換できます。

演算子:

  • <=, <, ==, !=, >=, >

警告

より大きなバイトサイズを使用する型、例えば bytes32 などを address に変換した場合、 address は切り捨てられます。 変換の曖昧さを減らすために、バージョン0.4.24以降のコンパイラでは、変換時に切り捨てを明示することを強制するようになっています。 例えば、32バイトの値 0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC を考えてみましょう。

address(uint160(bytes20(b))) を使うと 0x111122223333444455556666777788889999aAaa になり、 address(uint160(uint256(b))) を使うと 0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc になります。

注釈

EIP-55 に準拠した大文字小文字混在の16進数は自動的に address 型のリテラルとして扱われます。 アドレスリテラル を参照してください。

アドレスのメンバ

全メンバーのアドレスの早見表は、 アドレス型のメンバー を参照してください。

  • balancetransfer

プロパティ balance を使ってアドレスの残高を照会したり、 transfer 関数を使って支払先のアドレスにイーサ(wei単位)を送信したりすることが可能です。

address payable x = payable(0x123);
address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);

transfer 関数は、現在のコントラクトの残高が十分でない場合や、Ether送金が受信アカウントで拒否された場合に失敗します。 transfer 関数は失敗するとリバートします。

注釈

x がコントラクトアドレスの場合、そのコード(具体的には、 receive Ether関数 があればその receive Ether関数fallback関数 があればその fallback関数 )が transfer コールとともに実行されます(これはEVMの機能であり、防ぐことはできません)。 その実行がガス欠になるか、何らかの形で失敗した場合、Ether送金はリバートされ、現在のコントラクトは例外的に停止します。

  • send

send は、 transfer の低レベルのカウンターパートです。 実行に失敗した場合、現在のコントラクトは例外的に停止しませんが、 sendfalse を返します。

警告

send の使用にはいくつかの危険性があります。 コールスタックの深さが1024の場合(これは常に呼び出し側で強制できます)、送金は失敗し、また、受信者がガス欠になった場合も失敗します。 したがって、安全なEther送金を行うためには、 send の戻り値を常にチェックするか、 transfer を使用するか、あるいはさらに良い方法として、受信者がEtherを引き出すパターンを使用してください。

  • call, delegatecall, staticcall

ABIに準拠していないコントラクトとのインターフェースや、エンコーディングをより直接的に制御するために、関数 calldelegatecallstaticcall が用意されています。 これらの関数はすべて1つの bytes memory パラメータを受け取り、成功条件( bool )と戻りデータ( bytes memory )を返します。 関数 abi.encodeabi.encodePackedabi.encodeWithSelectorabi.encodeWithSignature は、構造化データのエンコードに使用できます。

例:

bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);

警告

これらの関数はすべて低レベルの関数であり、注意して使用する必要があります。 特に、未知のコントラクトは悪意を持っている可能性があり、それを呼び出すと、そのコントラクトに制御を渡すことになり、そのコントラクトが自分のコントラクトにコールバックする可能性があるので、コールが戻ってきたときの自分の状態変数の変化に備えてください。 他のコントラクトとやりとりする通常の方法は、コントラクトオブジェクト( x.f() )の関数を呼び出すことです。

注釈

以前のバージョンのSolidityでは、これらの関数が任意の引数を受け取ることができ、また、 bytes4 型の第1引数の扱いが異なっていました。 これらのエッジケースはバージョン0.5.0で削除されました。

gas モディファイアで供給ガスを調整することが可能です。

address(nameReg).call{gas: 1000000}(abi.encodeWithSignature("register(string)", "MyName"));

同様に、送金するEtherの値も制御できます。

address(nameReg).call{value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));

最後に、これらのモディファイアは組み合わせることができます。 その順番は問題ではありません。

address(nameReg).call{gas: 1000000, value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));

同様の方法で、関数 delegatecall を使用できます。 違いは、与えられたアドレスのコードのみが使用され、他のすべての側面(ストレージ、残高、...)は、現在のコントラクトから取得されます。 delegatecall の目的は、別のコントラクトに保存されているライブラリコードを使用することです。 ユーザーは、両方のコントラクトのストレージのレイアウトが、delegatecallを使用するのに適していることを確認しなければなりません。

注釈

Homestead以前のバージョンでは、 callcode という限定されたバリアントのみが利用可能で、オリジナルの msg.sendermsg.value の値にアクセスできませんでした。 この関数はバージョン0.5.0で削除されました。

Byzantiumから staticcall も使えるようになりました。 これは基本的に call と同じですが、呼び出された関数が何らかの形で状態を変更するとリバートされます。

calldelegatecallstaticcall の3つの関数は、非常に低レベルな関数で、Solidityの型安全性を壊してしまうため、 最後の手段 としてのみ使用してください。

gas オプションは3つの方式すべてで利用できますが、 value オプションは call でのみ利用できます。

注釈

スマートコントラクトのコードでは、状態の読み書きにかかわらず、ハードコードされたガスの値に依存することは、多くの落とし穴があるので避けたほうがよいでしょう。 また、ガスへのアクセスが将来変わる可能性もあります。

  • codecodehash

スマートコントラクトのデプロイコードをクエリできます。 .code を使用すると、EVMバイトコードを bytes memory として取得できます。 そのコードのkeccak-256ハッシュを取得するには .codehash を使用します( bytes32 として)。 なお、 addr.codehashkeccak256(addr.code) を使用するよりもコストが小さいです。

注釈

すべてのコントラクトは address 型に変換できるので、 address(this).balance を使って現在のコントラクトの残高を照会することが可能です。

コントラクト型

すべての コントラクト はそれ自身の型を定義します。 コントラクトを、それらが継承するコントラクトに暗黙的に変換できます。 コントラクトは、 address 型との間で明示的に変換できます。

address payable 型との間の明示的な変換は、コントラクト型にreceiveまたはpayableのフォールバック関数がある場合にのみ可能です。 変換は address(x) を使用して行われます。 コントラクト型にreceiveまたはpayment fallback関数がない場合、 address payable への変換は payable(address(x)) を使用して行うことができます。 詳細は、アドレス型 の項を参照してください。

注釈

バージョン0.5.0以前は、コントラクトはアドレス型から直接派生し、 addressaddress payable の区別はありませんでした。

コントラクト型( MyContract c )のローカル変数を宣言すると、そのコントラクトで関数を呼び出すことができます。 ただし、同じコントラクト型のどこかから代入するように注意してください。

また、コントラクトをインスタンス化することもできます(新規に作成することを意味します)。 詳細は 'Contracts via new' の項を参照してください。

コントラクトのデータ表現は address 型と同じで、この型は ABI でも使用されています。

コントラクトは、いかなる演算子もサポートしません。

コントラクト型のメンバは、 public とマークされた状態変数を含むコントラクトの外部関数です。

コントラクト C の場合は、 type(C) を使ってコントラクトに関する 型情報 にアクセスできます。

固定長バイト列

bytes1 , bytes2 , bytes3 , ..., bytes32 の値は、1から最大32までのバイト列を保持します。

演算子:

  • 比較: <=, <, ==, !=, >=, > ( bool に評価)

  • ビット演算子: &, |, ^ (ビットごとの排他的論理和), ~ (ビットごとの否定)

  • シフト演算子: << (左シフト), >> (右シフト)

  • インデックスアクセス: x が型 bytesI の場合、 0 <= k < I において x[k]k 番目のバイトを返します(読み取り専用)。

シフト演算子は、右オペランドに符号なし整数型を指定して動作します(ただし、左オペランドの型を返します)が、この型はシフトするビット数を表します。 符号付きの型でシフトするとコンパイルエラーになります。

メンバー:

  • .length は、バイト列の固定長を出力します(読み取り専用)。

注釈

bytes1[] 型はバイトの配列ですが、パディングのルールにより、各要素ごとに31バイトのスペースを無駄にしています(ストレージを除く)。 代わりに bytes 型を使うのが良いでしょう。

注釈

バージョン0.8.0以前では、 bytebytes1 のエイリアスでした。

動的サイズのバイト列

bytes:

動的なサイズのバイト配列。 配列 を参照。 値型ではありません!

string:

動的サイズのUTF-8エンコードされた文字列。 配列 を参照。 値型ではありません!

アドレスリテラル

アドレスチェックサムテストに合格した16進数リテラル(例: 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF )は address 型です。 16進数リテラルの長さが39桁から41桁の間で、チェックサムテストに合格しない場合はエラーになります。 エラーを取り除くには、ゼロを前置(整数型の場合)または後置(bytesNN型の場合)する必要があります。

注釈

混合ケースのアドレスチェックサムフォーマットは EIP-55 で定義されています。

有理数リテラルと整数リテラル

整数リテラルは、0~9の範囲の数字の列で構成されます。 小数点以下の数字として解釈されます。 例えば、 69 は69を意味します。 Solidityには8進数のリテラルは存在せず、先頭のゼロは無効です。

10進数の小数リテラルは小数点 . の後に少なくとも1つの数字で形成されます。 例としては .11.3 があります(ただし 1. は含まれません)。

2e10 のような科学的表記(指数表記)もサポートされています。 仮数は小数でも構いませんが、指数は整数でなければなりません。 MeE というリテラルは M * 10**E と同じ意味です。 例としては、 2e10-2e102e-102.5e1 などがあります。

アンダースコアは、読みやすくするために数値リテラルの桁を区切るのに使用できます。 例えば、10進法の 123_000 、16進法の 0x2eff_abde 、科学的10進法の 1_2e345_678 はすべて有効です。 アンダースコアは2つの数字の間にのみ使用でき、連続したアンダースコアは1つしか使用できません。 アンダースコアを含む数値リテラルには、追加の意味はなく、アンダースコアは無視されます。

数値リテラル式は、非リテラル型に変換されるまで(すなわち、数値リテラル式以外のもの(ブーリアンリテラルなど)と一緒に使用するか、明示的に変換することにより)、任意の精度を保持します。 このため、数値リテラル式では、計算がオーバーフローしたり、除算が切り捨てられたりすることはありません。

例えば、 (2**800 + 1) - 2**800 の結果は定数 1uint8 型)になりますが、中間の結果はマシンのワードサイズに収まりません。 さらに、 .5 * 8 の結果は整数の 4 になります(ただし、その間には非整数が使われています)。

警告

ほとんどの演算子はリテラルに適用するとリテラル式を生成するが、このパターンに従わない演算子もあります:

  • 三項演算子( ... ? ... : ...

  • 配列の添字( <array>[<index>]

255 + (true ? 1 : 0)255 + [1, 2, 3][0] のような式は、256というリテラルを直接使うのと同じだと思うかもしれませんが、実際には uint8 型の中で計算されるためオーバーフローする可能性があります。

整数に適用できる演算子は、オペランドが整数であれば、数リテラル式にも適用できます。 2つのうちいずれかが小数の場合、ビット演算は許可されず、指数が小数の場合、指数演算は許可されません(非有理数になってしまう可能性があるため)。

リテラル数を左(またはベース)オペランドとし、整数型を右(指数)オペランドとするシフトと指数計算は、右(指数)オペランドの型にかかわらず、常に uint256 (非負のリテラルの場合)または int256 (負のリテラルの場合)型で実行されます。

警告

バージョン0.4.0以前のSolidityでは、整数リテラルの除算は切り捨てられていましたが、有理数に変換されるようになりました。 つまり、 5 / 22 とはならず、 2.5 となります。

注釈

Solidityでは、有理数ごとに数リテラル型が用意されています。 整数リテラルと有理数リテラルは、数リテラル型に属します。 また、すべての数リテラル式(数リテラルと演算子のみを含む式)は、数リテラル型に属します。 つまり、数リテラル式 1 + 22 + 1 は、有理数3に対して同じ数リテラル型に属しています。

注釈

数リテラル式は、非リテラル式と一緒に使われると同時に、非リテラル型に変換されます。 型に関係なく、以下の b に割り当てられた式の値は整数と評価されます。 auint128 型なので、 2.5 + a という式は適切な型を持っていなければなりませんが。 2.5uint128 の型には共通の型がないので、Solidityのコンパイラはこのコードを受け入れません。

uint128 a = 1;
uint128 b = 2.5 + a + 0.5;

文字列リテラルと文字列型

文字列リテラルは、ダブルクオートまたはシングルクオート( "foo" または 'bar' )で記述され、連続した複数の部分に分割することもできます( "foo" "bar""foobar" に相当)。 これは長い文字列を扱う際に便利です。 また、C言語のように末尾にゼロを付けることはなく、 "foo" は4バイトではなく3バイトです。 整数リテラルと同様に、その型は様々ですが、 bytes1, ..., bytes32 に暗黙のうちに変換され、それが適合する場合は、 bytesstring に変換されます。

例えば、 bytes32 samevar = "stringliteral" では文字列リテラルが bytes32 型に割り当てられると、生のバイト形式で解釈されます。

文字列リテラルには、印刷可能なASCII文字のみを含めることができます。 つまり、0x20から0x7Eまでの文字です。

さらに、文字列リテラルは以下のエスケープ文字にも対応しています。

  • \<newline> (実際の改行のエスケープ)

  • \\ (バックスラッシュ)

  • \' (シングルクォート)

  • \" (ダブルクォート)

  • \n (改行)

  • \r (キャリッジリターン)

  • \t (タブ)

  • \xNN (ヘックスエスケープ、下記参照)

  • \uNNNN (Unicodeエスケープ、下記参照)

\xNN は16進数の値を受け取り、適切なバイトを挿入します。 \uNNNN はUnicodeコードポイントを受け取り、UTF-8シーケンスを挿入します。

注釈

バージョン0.8.0までは、さらに3つのエスケープシーケンスがありました。 \b\f\v です。 これらは他の言語ではよく使われていますが、実際にはほとんど必要ありません。 もし必要であれば、他のASCII文字と同じように16進数のエスケープ、すなわち \x08\x0c\x0b を使って挿入できます。

次の例の文字列の長さは10バイトです。 この文字列は、改行バイトで始まり、ダブルクォート、シングルクォート、バックスラッシュ文字、そして(セパレータなしで)文字列 abcdef が続きます。

"\n\"\'\\abc\
def"

改行ではないUnicodeの行終端記号(LF、VF、FF、CR、NEL、LS、PSなど)は、文字列リテラルを終了するものとみなされます。 改行が文字列リテラルを終了させるのは、その前に \ がない場合のみです。

Unicodeリテラル

通常の文字列リテラルはASCIIのみを含むことができますが、Unicodeリテラル(キーワード unicode を前に付けたもの)は、有効なUTF-8シーケンスを含むことができます。 また、Unicodeリテラルは、通常の文字列リテラルと同じエスケープシーケンスにも対応しています。

string memory a = unicode"Hello 😃";

16進数リテラル

16進数リテラルは、キーワード hex を前に付け、ダブルクオートまたはシングルクオートで囲みます( hex"001122FF"hex'0011_22_FF' )。 リテラルの内容は16進数でなければならず、バイト境界のセパレータとしてアンダースコアを1つ使用することも可能です。 リテラルの値は、16進数をバイナリ表現したものになります。

空白で区切られた複数の16進数リテラルは、1つのリテラルに連結されます。 hex"00112233" hex"44556677"hex"0011223344556677" と同じです。

16進数リテラルはある意味では、 文字列リテラル のように振る舞いますが、暗黙的に string 型に変換されるわけではありません。

列挙

列挙(enum)はSolidityでユーザー定義型を作成する一つの方法です。 すべての整数型との間で明示的に変換できますが、暗黙的な変換はできません。 整数型からの明示的な変換は、実行時に値が列挙型の範囲内にあるかどうかをチェックし、そうでない場合は パニックエラー を発生させます。 列挙型は少なくとも1つのメンバーを必要とし、宣言時のデフォルト値は最初のメンバーです。 列挙型は256以上のメンバーを持つことはできません。

データ表現は、C言語のenumと同じです。 オプションは、 0 から始まる後続の符号なし整数値で表されます。

type(NameOfEnum).mintype(NameOfEnum).max を使えば、与えられたenumの最小値と最大値を得ることができます。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;

contract test {
    enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
    ActionChoices choice;
    ActionChoices constant defaultChoice = ActionChoices.GoStraight;

    function setGoStraight() public {
        choice = ActionChoices.GoStraight;
    }

    // 列挙型はABIの一部ではないため、Solidityの外部に対して、「getChoice」の署名は自動的に「getChoice() returns (uint8)」に変更されることになります。
    function getChoice() public view returns (ActionChoices) {
        return choice;
    }

    function getDefaultChoice() public pure returns (uint) {
        return uint(defaultChoice);
    }

    function getLargestValue() public pure returns (ActionChoices) {
        return type(ActionChoices).max;
    }

    function getSmallestValue() public pure returns (ActionChoices) {
        return type(ActionChoices).min;
    }
}

注釈

Enumは、コントラクトやライブラリの定義とは別に、ファイルレベルで宣言することもできます。

ユーザー定義の値型

ユーザー定義の値型は、基本的な値型をゼロコストで抽象化して作成できます。 これは、エイリアスに似ていますが、型の要件がより厳しくなっています。

ユーザー定義の値型は、 type C is V を使って定義されます。 C は新しく導入される型の名前で、 V は組み込みの値の型(「基礎となる型」)でなければなりません。 関数 C.wrap は、基礎となる型からカスタム型への変換に使用されます。 同様に、関数 C.unwrap はカスタム型から基礎型への変換に使用されます。

C 型には、演算子や付属のメンバ関数がありません。 特に、演算子 == も定義されていません。 他の型との間の明示的および暗黙的な変換は許されません。

このような型の値のデータ表現は、基礎となる型から継承され、基礎となる型はABIでも使用されます。

次の例では、18桁の10進数固定小数点型を表すカスタム型 UFixed256x18 と、その型に対して算術演算を行うための最小限のライブラリを示しています。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;

// 18進数、256ビット幅の固定小数点型をユーザー定義の値型を使用して表現します。
type UFixed256x18 is uint256;

/// UFixed256x18に対して固定小数点演算を行うための最小限のライブラリ
library FixedMath {
    uint constant multiplier = 10**18;

    /// 2つのUFixed256x18の値を足します。
    /// uint256のチェックされた算術に依存して、オーバーフローでリバートします。
    function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) {
        return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
    }
    /// UFixed256x18の値とuint256の値を掛けます。
    /// uint256のチェックされた算術に依存して、オーバーフローでリバートします。
    function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) {
        return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
    }
    /// UFixed256x18の数のフロアを取ります。
    /// @return `a` を超えない最大の整数。
    function floor(UFixed256x18 a) internal pure returns (uint256) {
        return UFixed256x18.unwrap(a) / multiplier;
    }
    /// uint256 を同じ値の UFixed256x18 に変換します。
    /// 整数が大きすぎる場合はリバートします。
    function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) {
        return UFixed256x18.wrap(a * multiplier);
    }
}

UFixed256x18.wrapFixedMath.toUFixed256x18 は同じ署名を持っていますが、全く異なる2つの処理を行っていることに注目してください。 UFixed256x18.wrap 関数は入力と同じデータ表現の UFixed256x18 を返すのに対し、 toUFixed256x18 は同じ数値を持つ UFixed256x18 を返します。

関数型

関数型は、関数の型です。 関数型の変数は、関数から代入でき、関数型のパラメータは、関数呼び出しに関数を渡したり、関数呼び出しから関数を返したりするのに使われます。 関数型には、 内部(internal) 関数と 外部(external) 関数の2種類があります。

内部関数は、現在のコントラクトのコンテキストの外では実行できないため、現在のコントラクトの内部(より具体的には、現在のコードユニットの内部で、内部ライブラリ関数や継承された関数も含む)でのみ呼び出すことができます。 内部関数の呼び出しは、現在のコントラクトの関数を内部で呼び出す場合と同様に、そのエントリーラベルにジャンプすることで実現します。

外部関数は、アドレスと関数シグネチャで構成されており、外部関数呼び出しを介して渡したり、外部関数呼び出しから返したりできます。

関数型は以下のように表記されています。

function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]

パラメータ型とは対照的に、リターン型は空にできません。 関数型が何も返さない場合は、 returns (<return types>) の部分をすべて省略しなければなりません。

デフォルトでは、関数型は内部的なものなので、 internal キーワードは省略できます。 これは関数型にのみ適用されることに注意してください。 コントラクトで定義された関数については、ビジビリティを明示的に指定する必要があり、デフォルトはありません。

変換:

関数型 A は、それらのパラメータ型が同一であり、戻り値の型が同一であり、それらの内部/外部プロパティが同一であり、 A の状態の変更可能性が B の状態の変更可能性よりも制限されている場合に限り、関数型 B に暗黙的に変換可能です。 具体的には以下です。

  • pure 関数を viewnon-payable 関数に変換可能

  • view 関数から non-payable 関数への変換が可能

  • payable 関数から non-payable 関数への変換が可能

これら以外の関数型間の変換はできません。

payablenon-payable のルールは少しわかりにくいかもしれませんが、要するにある関数が payable であれば、ゼロのEtherの支払いも受け入れるということなので、 non-payable でもあるということです。 一方で、 non-payable 関数は送られてきたEtherを拒否するので、 non-payable 関数を payable 関数に変換することはできません。 明確にするために、etherを拒否することは、etherを拒否しないことよりも制限されます。 つまり、payableな関数をnon-payableな関数で上書きすることは可能ですが、その逆はできません。

さらに、 non-payable な関数ポインタを定義した場合、コンパイラは指定した関数が実際にEtherを拒否することを強制するわけではありません。 その代わりに、その関数ポインタは決して ether を送るために使われないことを強制します。 そのため、 payable な関数ポインタを non-payable な関数ポインタに割り当てることで、両方の型が同じように動作する、つまり、どちらもEtherを送信するために使用できないことを保証することが可能になります。

関数型変数が初期化されていない場合、それを呼び出すと パニックエラー になります。 また、関数に delete を使用した後に関数を呼び出した場合も同様です。

外部関数型がSolidityのコンテキスト外で使用される場合は、 function 型として扱われ、アドレスに続いて関数識別子をまとめて1つの bytes24 型にエンコードします。

現在のコントラクトのパブリック関数は、内部関数としても外部関数としても使用できることに注意してください。 f を内部関数として使用したい場合は f を、外部関数として使用したい場合は this.f を使用してください。

内部型の関数は、どこで定義されているかに関わらず、内部関数型の変数に代入できます。 これには、コントラクトとライブラリの両方のプライベート関数、内部関数、パブリック関数のほか、フリーの関数も含まれます。 一方、外部関数型は、パブリック関数と外部コントラクト関数にのみ対応しています。

注釈

calldata パラメータを持つ外部関数は、 calldata パラメータを持つ外部関数型と互換性がありません。 代わりに memory パラメータを持つ対応する型と互換性があります。 例えば、 function (string calldata) external 型の値が指すことのできる関数はありませんが、 function (string memory) externalfunction f(string memory) external {}function g(string calldata) external {} を指すことができます。 これは、どちらの場所でも、引数が同じように関数に渡されるからです。 呼び出し元はcalldataを直接外部関数に渡すことはできず、常に引数をメモリにABIエンコードします。 パラメータを calldata としてマークすることは、外部関数の実装にのみ影響し、呼び出し側の関数ポインタでは意味を持ちません。

ライブラリは、 delegatecallセレクタへの異なるABI規約 の使用を必要とするため、除外されます。 インターフェースで宣言された関数は定義を持たないので、それを指し示すことも意味がありません。

メンバー:

外部(またはパブリック)関数には、次のようなメンバーを持ちます。

  • .address は、関数のコントラクトのアドレスを返します。

  • .selectorABI関数セレクタ を返します。

注釈

外部(またはパブリック)関数には、追加のメンバー .gas(uint).value(uint) がありました。 これらはSolidity 0.6.2で非推奨となり、Solidity 0.7.0で削除されました。 代わりに {gas: ...}{value: ...} を使って、それぞれ関数に送られるガスの量やweiの量を指定してください。 詳細は 外部関数呼び出し を参照してください。

メンバーの使用法を示す例:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.4 <0.9.0;

contract Example {
    function f() public payable returns (bytes4) {
        assert(this.f.address == address(this));
        return this.f.selector;
    }

    function g() public {
        this.f{gas: 10, value: 800}();
    }
}

内部関数型の使用法を示す例:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

library ArrayUtils {
    // 内部関数は、同じコードコンテキストの一部となるため、内部ライブラリ関数で使用できる
    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;
    }
}

外部関数型を使用するもう一つの例:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;

contract Oracle {
    struct Request {
        bytes data;
        function(uint) external callback;
    }

    Request[] private 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 private ORACLE_CONST = Oracle(address(0x00000000219ab540356cBB839Cbe05303d7705Fa)); // known contract
    uint private exchangeRate;

    function buySomething() public {
        ORACLE_CONST.query("USD", this.oracleResponse);
    }

    function oracleResponse(uint response) public {
        require(
            msg.sender == address(ORACLE_CONST),
            "Only oracle can call this."
        );
        exchangeRate = response;
    }
}

注釈

ラムダ関数やインライン関数が予定されていますが、まだサポートされていません。

参照型

参照型の値は、複数の異なる名前で変更できます。 値型の変数が使われるたびに独立したコピーを得ることができる値型とは対照的です。 そのため、参照型は値型よりも慎重に扱う必要があります。 現在、参照型は構造体、配列、マッピングで構成されています。 参照型を使用する場合は、その型が格納されているデータ領域を常に明示的に提供する必要があります。 memory (ライフタイムが外部の関数呼び出しに限定される)、 storage (状態変数が格納されている場所で、ライフタイムがコントラクトのライフタイムに限定される)、 calldata (関数の引数が格納されている特別なデータの場所)のいずれかになります。

データロケーションを変更する割り当てや型変換は、常に自動コピー操作が発生しますが、同じデータロケーション内の割り当ては、ストレージ型の場合、一部のケースでしかコピーされません。

データロケーション

すべての参照型には、それがどこに保存されているかについて、「データロケーション」という追加のアノテーションがあります。 データロケーションは3つあります。 memorystoragecalldata です。 コールデータは、関数の引数が格納される、変更不可能で永続性のない領域で、ほとんどメモリのように動作します。

注釈

できれば、データの場所として calldata を使うようにしましょう。 コピーを避けることができますし、データが変更されないようにすることもできます。 calldata 型のデータを持つ配列や構造体は、関数から返すことができますが、そのような型を代入することはできません。

注釈

バージョン0.6.9以前では、参照型引数のデータロケーションは、外部関数では calldata 、パブリック関数では memory 、内部およびプライベート関数では memory または storage に制限されていました。 現在では、 memorycalldata は、そのビジビリティに関わらず、すべての関数で許可されています。

注釈

バージョン0.5.0までは、データロケーションを省略でき、変数の種類や関数の種類などに応じて異なるロケーションをデフォルトとしていましたが、現在ではすべての複合型でデータのロケーションを明示的に指定しなければなりません。

データロケーションと代入の挙動

データロケーションは、データの永続性だけでなく、代入のセマンティクスにも関係します。

  • storagememory の間(または calldata から)の代入では、常に独立したコピーが作成されます。

  • memory から memory への代入では、参照のみが作成されます。 つまり、あるメモリ変数の変更は、同じデータを参照している他のすべてのメモリ変数にも反映されるということです。

  • storage から ローカル ストレージ変数への代入もまた、参照のみを代入します。

  • その他の storage への代入は、常にコピーされます。 このケースの例としては、状態変数やストレージ構造体型のローカル変数のメンバへの代入がありますが、ローカル変数自体が単なる参照であっても同様です。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;

contract C {
    // xのデータロケーションはstorageです。
    // データロケーションを省略できるのはここだけです。
    uint[] x;

    // memoryArray のデータロケーションはメモリです。
    function f(uint[] memory memoryArray) public {
        x = memoryArray; // 配列全体をストレージにコピーします。
        uint[] storage y = x; // ポインタが代入され、yのデータロケーションはストレージになります。
        y[7]; // 8番目の要素を返します。
        y.pop(); // yを通してxも修正されます。
        delete x; // 配列がクリアされ、yも修正されます。
        // ストレージに新しい一時的な無名配列を作成する必要がありますが、ストレージは「静的」に割り当てられているため、次のようにするとうまくいきません。
        // y = memoryArray;
        // Similarly, "delete y" is not valid, as assignments to local variables
        // referencing storage objects can only be made from existing storage objects.
        // It would "reset" the pointer, but there is no sensible location it could point to.
        // For more details see the documentation of the "delete" operator.
        // delete y;
        g(x); // gを呼び出し、xへの参照を渡します。
        h(x); // hを呼び出し、独立した一時的なコピーをメモリ上に作成します。
    }

    function g(uint[] storage) internal pure {}
    function h(uint[] memory) public pure {}
}

配列

配列は、コンパイル時に固定されたサイズも動的なサイズも持つことができます。

固定サイズ k 、要素型 T の配列の型は T[k] 、動的サイズの配列は T[] と書きます。

例えば、 uint の動的配列を5個並べた配列は uint[][5] と書きます。 この表記法は、他のいくつかの言語とは逆になっています。 Solidityでは、たとえ X がそれ自体配列であっても、 X[3] は常に X 型の3つの要素を含む配列です。 これは、Cなどの他の言語ではそうではありません。

インデックスはゼロベースで、アクセスは宣言とは逆方向になります。

例えば、変数 uint[][5] memory x がある場合、3番目の動的配列の7番目の uint にアクセスするには x[2][6] を使い、3番目の動的配列にアクセスするには x[2] を使います。 繰り返しになりますが、配列にもなる T 型に対して配列 T[5] a がある場合、 a[2] は常に T 型です。

配列の要素は、マッピングや構造体など、どのような型でもよいです。 一般的な型の制限が適用され、マッピングは storage データの場所にしか保存できず、一般に公開されている関数には ABI型 のパラメータが必要となります。

状態変数の配列に public をマークして、Solidityに ゲッター を作成させることが可能です。 数値インデックスは、getterの必須パラメータとなります。

配列の終端を超えてアクセスすると、アサーションが失敗します。 メソッド .push().push(value) は、動的なサイズの配列の末尾に新しい要素を追加するために使用できます。

注釈

動的なサイズの配列は、ストレージ内でのみサイズを変更できます。 メモリ上では、このような配列は任意のサイズにできますが、配列が割り当てられるとサイズを変更できません。

配列としての bytesstring

bytes 型と string 型の変数は、特殊な配列です。 bytes 型は bytes1[] と似ていますが、calldataとメモリにしっかりと詰め込まれています。 stringbytes と同じですが、長さやインデックスのアクセスはできません。

Solidityには文字列操作関数はありませんが、サードパーティ製の文字列ライブラリがあります。 また、 keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2)) を使って2つの文字列をそのkeccak256-hashで比較したり、 string.concat(s1, s2) を使って2つの文字列を連結できます。

memorybytes1[] を使うと要素間に31個のパディングバイトを追加するので、 bytes1[] よりも bytes を使用した方が安価です。 なお、 storage では、タイトパッキングのためパディングは存在しません。 bytesとstring を参照してください。 原則として、任意の長さの生バイトデータには bytes を、任意の長さの文字列(UTF-8)データには string を使用してください。 長さを一定のバイト数に制限できる場合は、値型 bytes1bytes32 のいずれかを必ず使用してください。 その方がはるかに安価です。

注釈

s という文字列のバイト表現にアクセスしたい場合は、 bytes(s).length / bytes(s)[7] = 'x'; を使います。 UTF-8表現の低レベルバイトにアクセスしているのであって、個々の文字にアクセスしているわけではないことに注意してください。

bytes.concat 関数と string.concat 関数

string.concat を使えば、任意の数の string の値を連結できます。 この関数は、引数の内容をパディングせずに格納した単一の string memory を返します。 暗黙のうちに string に変換できない他の型のパラメータを使用したい場合は、まず string に変換する必要があります。

同様に、 bytes.concat 関数は、任意の数の bytes または bytes1 ... bytes32 値を連結させることができます。 この関数は、引数の内容をパディングせずに格納した単一の bytes memory を返します。 文字列パラメータや、暗黙のうちに bytes に変換できない他の型を使用したい場合は、最初に bytes または bytes1/.../bytes32 に変換する必要があります。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

contract C {
    string s = "Storage";
    function f(bytes calldata bc, string memory sm, bytes16 b) public view {
        string memory concatString = string.concat(s, string(bc), "Literal", sm);
        assert((bytes(s).length + bc.length + 7 + bytes(sm).length) == bytes(concatString).length);

        bytes memory concatBytes = bytes.concat(bytes(s), bc, bc[:2], "Literal", bytes(sm), b);
        assert((bytes(s).length + bc.length + 2 + 7 + bytes(sm).length + b.length) == concatBytes.length);
    }
}

引数なしで string.concat あるいは bytes.concat を呼び出すと、空の配列が返されます。

メモリ配列のアロケート

動的な長さを持つメモリ配列は、 new 演算子を使って作成できます。 ストレージ配列とは対照的に、メモリ配列のサイズを変更できません(例えば、 .push メンバ関数は使用できません)。 必要なサイズを事前に計算するか、新しいメモリ配列を作成してすべての要素をコピーする必要があります。

Solidityのすべての変数と同様に、新しく割り当てられた配列の要素は、常に デフォルト値 で初期化されます。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.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;
    }
}

配列リテラル

配列リテラルは、1つまたは複数の式を角括弧( [...] )で囲んだコンマ区切りのリストです。 例えば、 [1, a, f(3)] です。 配列リテラルの型は以下のように決定されます。

これは、常に静的サイズのメモリ配列で、その長さは式の数です。

配列の基本型は、リストの最初の式の型で、他のすべての式が暗黙的に変換できるようになっています。 これができない場合は型エラーとなります。

すべての要素に変換できる型があるだけでは不十分です。 要素の一つがその型でなければなりません。

下の例では、それぞれの定数の型が uint8 であることから、 [1, 2, 3] の型は uint8[3] memory となります。 結果を uint[3] memory 型にしたい場合は、最初の要素を uint に変換する必要があります。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract C {
    function f() public pure {
        g([uint(1), 2, 3]);
    }
    function g(uint[3] memory) public pure {
        // ...
    }
}

配列リテラル [1, -1] が無効なのは、最初の式の型が uint8 であるのに対し、2番目の式の型が int8 であり、両者を暗黙的に変換できないからです。 これを動作させるには、例えば [int8(1), -1] を使用します。

異なる型の固定サイズのメモリ配列は、(基底型が変換できても)相互に変換できないため、二次元配列リテラルを使用する場合は、常に共通の基底型を明示的に指定する必要があります。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract C {
    function f() public pure returns (uint24[2][4] memory) {
        uint24[2][4] memory x = [[uint24(0x1), 1], [0xffffff, 2], [uint24(0xff), 3], [uint24(0xffff), 4]];
        // 以下のようにすると、内側の配列の一部が正しい型でないため、うまくいきません。
        // uint[2][4] memory x = [[0x1, 1], [0xffffff, 2], [0xff, 3], [0xffff, 4]];
        return x;
    }
}

固定サイズのメモリ配列を、動的サイズのメモリ配列に代入することはできません。 つまり、以下のことはできません。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

// これではコンパイルできません。
contract C {
    function f() public {
        // 次の行は、uint[3]メモリをuint[]メモリに変換できないため、型エラーが発生します。
        uint[] memory x = [uint(1), 3, 4];
    }
}

将来はこの制限を解除する予定ですが、ABIでの配列の渡し方の関係で複雑になっています。

動的なサイズの配列を初期化したい場合は、個々の要素を代入する必要があります。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract C {
    function f() public pure {
        uint[] memory x = new uint[](3);
        x[0] = 1;
        x[1] = 3;
        x[2] = 4;
    }
}

配列のメンバー

length:

配列は、要素数を表す length メンバを持ちます。 メモリー配列の長さは、作成時に固定されます(ただし、動的、つまり実行時のパラメータに依存することがあります)。

push():

動的ストレージ配列と bytesstring ではありません)には、 push() というメンバ関数があり、配列の最後にゼロ初期化された要素を追加するのに使用できます。 この関数は、要素への参照を返すので、 x.push().t = 2x.push() = b のように使用できます。

push(x):

動的ストレージ配列と bytes``( ``string ではありません)には、 push(x) というメンバ関数があり、配列の最後に与えられた要素を追加するのに使用できます。 この関数は何も返しません。

pop():

動的ストレージ配列と bytesstring ではありません)には pop() というメンバ関数があり、配列の最後から要素を削除するのに使用できます。 この関数は、削除された要素に対して delete を暗黙的に呼び出します。 この関数は何も返しません。

注釈

push() をコールしてストレージ配列の長さを増加させると、ストレージがゼロ初期化されるため、ガスコストが一定になります。 一方、 pop() をコールして長さを減少させると、削除される要素の「サイズ」に依存するコストが発生します。 その要素が配列の場合は、 delete を呼び出すのと同様に、削除された要素を明示的にクリアすることが含まれるため、非常にコストがかかります。

注釈

配列の配列を(publicではなく)外部関数で使用するには、ABI coder v2を有効にする必要があります。

注釈

Byzantium以前のEVMバージョンでは、関数呼び出しから返される動的配列にアクセスできませんでした。 動的配列を返す関数を呼び出す場合は、必ずByzantiumモードに設定されたEVMを使用してください。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

contract ArrayContract {
    uint[2**20] aLotOfIntegers;
    // 以下は動的配列のペアではなく、ペアの動的配列(つまり長さ2の固定サイズ配列のペア)であることに注意してください。
    // In Solidity, T[k] and T[] are always arrays with elements of type T, even if T itself is an array.
    // Because of that, bool[2][] is a dynamic array of elements that are bool[2]. This is different from other languages, like C.
    // すべての状態変数のデータロケーションはストレージです。
    bool[2][] pairsOfFlags;

    // newPairsはメモリに格納されます - パブリックコントラクト関数の引数として唯一の選択肢です。
    function setAllFlagPairs(bool[2][] memory newPairs) public {
        // ストレージ配列への代入は、 ``newPairs`` のコピーを実行し、完全な配列 ``pairsOfFlags`` を置き換えます。
        pairsOfFlags = newPairs;
    }

    struct StructType {
        uint[] contents;
        uint moreInfo;
    }
    StructType s;

    function f(uint[] memory c) public {
        // ``s`` への参照を ``g`` に格納します。
        StructType storage g = s;
        // ``s.moreInfo`` も変更します。
        g.moreInfo = 2;
        // コピーを代入します。
        // なぜなら ``g.contents`` はローカル変数ではなく、ローカル変数のメンバだからです。
        g.contents = c;
    }

    function setFlagPair(uint index, bool flagA, bool flagB) public {
        // 存在しないインデックスにアクセスすると、例外が発生します。
        pairsOfFlags[index][0] = flagA;
        pairsOfFlags[index][1] = flagB;
    }

    function changeFlagArraySize(uint newSize) public {
        // 配列の長さを変更するには、push と pop を使用するのが唯一の方法です。
        if (newSize < pairsOfFlags.length) {
            while (pairsOfFlags.length > newSize)
                pairsOfFlags.pop();
        } else if (newSize > pairsOfFlags.length) {
            while (pairsOfFlags.length < newSize)
                pairsOfFlags.push();
        }
    }

    function clear() public {
        // これらは、配列を完全にクリアします。
        delete pairsOfFlags;
        delete aLotOfIntegers;
        // これも同じ効果です。
        pairsOfFlags = new bool[2][](0);
    }

    bytes byteData;

    function byteArrays(bytes memory data) public {
        // バイト配列("bytes")はパディングなしで格納されるため異なりますが、"uint8[]"と同じように扱うことができます。
        byteData = data;
        for (uint i = 0; i < 7; i++)
            byteData.push();
        byteData[3] = 0x08;
        delete byteData[2];
    }

    function addFlag(bool[2] memory flag) public returns (uint) {
        pairsOfFlags.push(flag);
        return pairsOfFlags.length;
    }

    function createMemoryArray(uint size) public pure returns (bytes memory) {
        // 動的メモリ配列は `new` を用いて作成します。
        uint[2][] memory arrayOfPairs = new uint[2][](size);

        // インライン配列は常に静的サイズであり、リテラルのみを使用する場合は、少なくとも1つの型を提供する必要があります。
        arrayOfPairs[0] = [uint(1), 2];

        // 動的バイト列を作成します。
        bytes memory b = new bytes(200);
        for (uint i = 0; i < b.length; i++)
            b[i] = bytes1(uint8(i));
        return b;
    }
}

文字列の要素へのダングリング参照

文字列を扱う場合、ぶら下がり参照を避けるように注意する必要があります。 ぶら下がり参照とは、もはや存在しないものを指す参照、あるいは参照を更新せずに移動された参照のことです。 例えば、ローカル変数に配列の要素への参照を格納した後、格納されている配列から .pop() を行うと、ダングリング参照が発生する可能性があります:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

contract C {
    uint[][] s;

    function f() public {
        // Stores a pointer to the last array element of s.
        uint[] storage ptr = s[s.length - 1];
        // Removes the last array element of s.
        s.pop();
        // Writes to the array element that is no longer within the array.
        ptr.push(0x42);
        // Adding a new element to ``s`` now will not add an empty array, but
        // will result in an array of length 1 with ``0x42`` as element.
        s.push();
        assert(s[s.length - 1][0] == 0x42);
    }
}

ptr.push(0x42) の書き込みは、 ptr がもはや s の有効な要素を指していないにもかかわらず、 リバートしません 。 コンパイラは、未使用のストレージは常にゼロになると仮定しているので、その後の s.push() はストレージに明示的にゼロを書き込むことはありません。 したがって、 push() の後の s の最後の要素は、長さが 1 で、最初の要素として 0x42 を含むことになります。

Solidityでは、ストレージ内の値型への参照を宣言できないことに注意してください。 これらの種類の明示的なダングリング参照は、ネストされた参照型に制限されます。 しかし、タプル代入で複雑な式を使用する場合、ダングリング参照も一時的に発生する可能性があります:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

contract C {
    uint[] s;
    uint[] t;
    constructor() {
        // Push some initial values to the storage arrays.
        s.push(0x07);
        t.push(0x03);
    }

    function g() internal returns (uint[] storage) {
        s.pop();
        return t;
    }

    function f() public returns (uint[] memory) {
        // The following will first evaluate ``s.push()`` to a reference to a new element
        // at index 1. Afterwards, the call to ``g`` pops this new element, resulting in
        // the left-most tuple element to become a dangling reference. The assignment still
        // takes place and will write outside the data area of ``s``.
        (s.push(), g()[0]) = (0x42, 0x17);
        // A subsequent push to ``s`` will reveal the value written by the previous
        // statement, i.e. the last element of ``s`` at the end of this function will have
        // the value ``0x42``.
        s.push();
        return s;
    }
}

ストレージへの代入はステートメントごとに1回だけにして、代入の左辺に複雑な式を書かないようにするのが常に安全です。

bytes配列の要素への参照を扱う場合は特に注意が必要です。 bytes配列に .push() を行うと、ストレージ内のレイアウトが shortからlongに切り替わる 可能性があるからです。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

// This will report a warning
contract C {
    bytes x = "012345678901234567890123456789";

    function test() external returns(uint) {
        (x.push(), x.push()) = (0x01, 0x02);
        return x.length;
    }
}

ここで、最初の x.push() が評価されたとき、 x はまだショートレイアウトで格納されており、 x.push()x の最初の格納スロットの要素への参照を返します。 しかし、2番目の x.push() はバイト配列をラージレイアウトに切り替えます。 このとき、 x.push() が参照していた要素は配列のデータエリアにあるのですが、参照は元の体の元を指しています。 安全のために、1回の代入でバイト配列を最大1要素だけ大きくし、同じステートメントで同時に配列にインデックスアクセスしないようにしてください。

上記は、現在のバージョンのコンパイラにおけるダングリングストレージ参照の動作について説明したものですが、ダングリング参照を含むコードはすべて、 未定義の動作 を持つと考えるべきです。 特に、将来のバージョンのコンパイラーは、ダングリング参照を含むコードの動作を変更する可能性があるということです。

コードの中でダングリング参照を避けるようにしてください!

配列のスライス

配列のスライスは、配列の連続した部分のビューです。 スライスは x[start:end] と書き、 startend はuint256型になる(または暗黙のうちに変換できる)式です。 スライスの最初の要素は x[start] で、最後の要素は x[end - 1] です。

startend より大きい場合や、 end が配列の長さより大きい場合は、例外が発生します。

startend はどちらもオプションです。 start はデフォルトで 0end はデフォルトで配列の長さになります。

配列スライスは、メンバーを持ちません。 スライスは、基礎となる型の配列に暗黙的に変換可能で、インデックスアクセスをサポートします。 インデックスアクセスは、基礎となる配列での絶対的なものではなく、スライスの開始点からの相対的なものです。

配列スライスには型名がありません。 つまり、どの変数も配列スライスを型として持つことはできず、中間式にのみ存在することになります。

注釈

現在、配列スライスはcalldata配列に対してのみ実装されています。

配列スライスは、関数のパラメータで渡された二次データをABIデコードするのに便利です。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.5 <0.9.0;
contract Proxy {
    /// @dev プロキシ(すなわちこのコントラクト)で管理するクライアントコントラクトのアドレス
    address client;

    constructor(address client_) {
        client = client_;
    }

    /// 引数のアドレスの基本的な検証を行った後、クライアントが実装する"setOwner(address)"のフォワードコール
    function forward(bytes calldata payload) external {
        bytes4 sig = bytes4(payload[:4]);
        // 切り捨て処理のため、bytes4(payload)も同じ処理
        // bytes4 sig = bytes4(payload);
        if (sig == bytes4(keccak256("setOwner(address)"))) {
            address owner = abi.decode(payload[4:], (address));
            require(owner != address(0), "Address of owner cannot be zero.");
        }
        (bool status,) = client.delegatecall(payload);
        require(status, "Forwarded call failed.");
    }
}

構造体

Solidityでは、構造体の形で新しい型を定義する方法を提供しており、次の例のようになります。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

// 2つのフィールドを持つ新しい型を定義します。
// 構造体をコントラクトの外部で宣言すると、複数のコントラクトで共有できるようになります。
// ここでは、これはあまり必要ありません。
struct Funder {
    address addr;
    uint amount;
}

contract CrowdFunding {
    // 構造体はコントラクトの内部で定義することもでき、その場合、その内部および派生コントラクトでのみ認識できるようになります。
    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は返り値です。
        // "campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0)"は、
        // 右側がマッピングを含むメモリ構造体"Campaign"を作成するため、使用することはできません。
        Campaign storage c = campaigns[campaignID];
        c.beneficiary = beneficiary;
        c.fundingGoal = goal;
    }

    function contribute(uint campaignID) public payable {
        Campaign storage c = campaigns[campaignID];
        // 指定された値で初期化された新しい一時的なメモリ構造体を作成し、ストレージにコピーします。
        // Funder(msg.sender, msg.value) を使用して初期化することもできます。
        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 のようにローカル変数に代入せずに、構造体のメンバーに直接アクセスすることもできます。

注釈

Solidity 0.7.0までは、ストレージのみの型(マッピングなど)のメンバーを含むメモリ構造が許可されており、上の例の campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0) のような代入が機能し、それらのメンバーを静かにスキップしていました。

マッピング型

マッピング型は mapping(KeyType KeyName? => ValueType ValueName?) という構文を使い、マッピング型の変数は mapping(KeyType KeyName? => ValueType ValueName?) VariableName という構文を使います。 KeyType は組み込みの値型、 bytesstring 、コントラクト型やenum型が使用できます。 マッピング、構造体、配列型など、その他のユーザー定義型や複雑な型は使用できません。 ValueType にはマッピング、配列、構造体を含む任意の型を指定できます。 KeyNameValueName はオプションであり、型以外の任意の有効な識別子を指定できます( mapping(KeyType => ValueType) も同様に機能します)。

マッピングは ハッシュテーブル と考えることができ、ありとあらゆるキーが存在するように仮想的に初期化され、バイト表現がすべてゼロである値(型の デフォルト値 )にマッピングされています。 キーデータはマッピングには保存されず、 keccak256 ハッシュのみが値の検索に使用されるという点で似ています。

このため、マッピングには長さや、キーや値が設定されているという概念がなく、割り当てられたキーに関する余分な情報( マッピングのクリア 参照)がないと消すことができません。

マッピングのデータロケーションは storage のみであるため、状態変数、関数内のストレージ参照型、ライブラリ関数のパラメータとして使用できます。 これらは、一般に公開されているコントラクト関数のパラメータやリターンパラメータとしては使用できません。 これらの制限は、マッピングを含む配列や構造体にも当てはまります。

マッピング型の状態変数を public としてマークすると、Solidityはあなたに代わって ゲッター を作成します。 KeyTypeKeyName (指定された場合)という名前のゲッターのパラメータになります。 もし ValueType が値型または構造体であれば、ゲッターは ValueTypeValueName という名前で返します。 もし ValueType が配列またはマッピングの場合、ゲッターは各 KeyType に対して再帰的に1つのパラメータを持ちます。

以下の例では、 MappingExample コントラクトがパブリック balances マッピングを定義しており、キー型は address 、値型は uint で、Ethereumアドレスを符号なし整数値にマッピングしています。 uint は値型なので、ゲッターは型にマッチした値を返しますが、 MappingUser コントラクトでは指定されたアドレスの値を返しているのがわかります。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.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));
    }
}

下の例は、 ERC20トークン を簡略化したものです。 _allowances は、別のマッピング型の中にマッピング型がある例です。

以下の例では、オプションの KeyNameValueName をマッピングに指定しています。 これはコントラクトの機能やバイトコードには影響せず、マッピングのゲッターのABIの入力と出力に name フィールドを設定するだけです。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.18;

contract MappingExampleWithNames {
    mapping(address user => uint balance) public balances;

    function update(uint newBalance) public {
        balances[msg.sender] = newBalance;
    }
}

以下の例では、 _allowances を使って、他の人があなたのアカウントから引き出すことができる金額を記録しています。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;

contract MappingExample {

    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }

    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        require(_allowances[sender][msg.sender] >= amount, "ERC20: Allowance not high enough.");
        _allowances[sender][msg.sender] -= amount;
        _transfer(sender, recipient, amount);
        return true;
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        require(_balances[sender] >= amount, "ERC20: Not enough funds.");

        _balances[sender] -= amount;
        _balances[recipient] += amount;
        emit Transfer(sender, recipient, amount);
    }
}

イテレート可能なマッピング

マッピングはイテレートできません。 つまり、キーを列挙することもできません。 しかし、マッピングの上にデータ構造を実装し、その上で反復処理を行うことは可能です。 例えば、以下のコードでは、 IterableMapping ライブラリを実装し、 User コントラクトがデータを追加し、 sum 関数がすべての値を合計するために反復処理を行います。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;

struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }

struct itmap {
    mapping(uint => IndexValue) data;
    KeyFlag[] keys;
    uint size;
}

type Iterator is uint;

library IterableMapping {
    function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {
        uint keyIndex = self.data[key].keyIndex;
        self.data[key].value = value;
        if (keyIndex > 0)
            return true;
        else {
            keyIndex = self.keys.length;
            self.keys.push();
            self.data[key].keyIndex = keyIndex + 1;
            self.keys[keyIndex].key = key;
            self.size++;
            return false;
        }
    }

    function remove(itmap storage self, uint key) internal returns (bool success) {
        uint keyIndex = self.data[key].keyIndex;
        if (keyIndex == 0)
            return false;
        delete self.data[key];
        self.keys[keyIndex - 1].deleted = true;
        self.size --;
    }

    function contains(itmap storage self, uint key) internal view returns (bool) {
        return self.data[key].keyIndex > 0;
    }

    function iterateStart(itmap storage self) internal view returns (Iterator) {
        return iteratorSkipDeleted(self, 0);
    }

    function iterateValid(itmap storage self, Iterator iterator) internal view returns (bool) {
        return Iterator.unwrap(iterator) < self.keys.length;
    }

    function iterateNext(itmap storage self, Iterator iterator) internal view returns (Iterator) {
        return iteratorSkipDeleted(self, Iterator.unwrap(iterator) + 1);
    }

    function iterateGet(itmap storage self, Iterator iterator) internal view returns (uint key, uint value) {
        uint keyIndex = Iterator.unwrap(iterator);
        key = self.keys[keyIndex].key;
        value = self.data[key].value;
    }

    function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns (Iterator) {
        while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
            keyIndex++;
        return Iterator.wrap(keyIndex);
    }
}

// 使用方法
contract User {
    // データを保持する構造体
    itmap data;
    // データ型にライブラリ関数を適用します。
    using IterableMapping for itmap;

    // 何かを挿入する
    function insert(uint k, uint v) public returns (uint size) {
        // これは IterableMapping.insert(data, k, v) を呼び出します。
        data.insert(k, v);
        // 構造体のメンバーにアクセスすることは可能ですが、構造体をいじらないように注意する必要があります。
        return data.size;
    }

    // 保存されているすべてのデータの合計を計算します。
    function sum() public view returns (uint s) {
        for (
            Iterator i = data.iterateStart();
            data.iterateValid(i);
            i = data.iterateNext(i)
        ) {
            (, uint value) = data.iterateGet(i);
            s += value;
        }
    }
}

演算子

算術演算子やビット演算子は、2つのオペランドが同じ型でなくても適用できます。 例えば、 y = x + zxuint8 で、 zuint32 という型を持っていても計算できます。 このような場合、次のようなメカニズムで、演算が計算される型(これはオーバーフローの場合に重要です)と演算子の結果の型を決定します:

  1. 右オペランドの型が左オペランドの型に暗黙のうちに変換できる場合、左オペランドの型を使用します。

  2. 左オペランドの型が右オペランドの型に暗黙のうちに変換できる場合、右オペランドの型を使用します。

  3. それ以外の場合は、演算ができません。

オペランドの一方が リテラルの数値 の場合、まずその値を保持できる最小の型である「モバイル型」に変換されます(同じビット幅の符号なし型は符号付き型より「小さい」とみなされます)。 両方がリテラルの数値の場合、演算は事実上無制限の精度で計算されます。 つまり、式は必要な限りの精度で評価され、結果がリテラルでない型で使われたときに失われることはありません。

演算子の結果の型は、結果が常に bool になる比較演算子を除いて、演算が行われた型と同じです。

演算子 ** (指数)、 <<>> は、演算と結果において左オペランドの型を使用します。

三項演算子

三項演算子は、 <expression> ? <trueExpression> : <falseExpression> という形式の式で使われます。 これは、メインの <expression> の評価結果に応じて、後者の2つの式のうち1つを評価するものです。 もし <expression>true と評価されれば <trueExpression> が評価され、そうでなければ <falseExpression> が評価されます。

三項演算子の結果は、たとえすべてのオペランドが有理数リテラルであっても、有理数型を持ちません。 結果の型は、上記と同じように2つのオペランドの型から決定され、必要であれば最初にその移動型に変換されます。

その結果、 255 + (true ? 1 : 0) は算術オーバーフローによりリバートされてしまいます。 これは (true ? 1 : 0)uint8 型であるため、加算も uint8 で行う必要があり、256はこの型で許される範囲を超えているためです。

もう一つの結果として、 1.5 + 1.5 のような式は有効だが、 1.5 + (true ? 1.5 : 2.5) は無効です。 これは、前者が無制限の精度で評価される有理式であり、その最終値のみが重要だからです。 後者は小数有理数から整数への変換を伴うので、現在では認められていません。

複合演算子、インクリメント/デクリメント演算子

a がLValue(すなわち変数や代入可能なもの)の場合、以下の演算子がショートハンドとして利用できます。

a += ea = a + e と同等です。 演算子 -=*=/=%=|=&=^=<<=>>= はそれぞれ定義されています。 a++a--a += 1 / a -= 1 に相当しますが、表現自体は a の前の値のままです。 一方、 --a++a は、 a に同じ効果を与えますが、変更後の値を返します。

delete

delete a は、 a に型の初期値を割り当てます。 つまり、整数の場合は a = 0 と同じですが、配列にも使用でき、長さ0の動的配列や、すべての要素を初期値に設定した同じ長さの静的配列を割り当てます。 delete a[x] は、配列のインデックス x の項目を削除し、他のすべての要素と配列の長さはそのままにします。 これは特に、配列にギャップを残すことを意味します。 アイテムを削除する予定なら、 マッピング の方が良いでしょう。

構造体の場合は、すべてのメンバーがリセットされた構造体が割り当てられます。 つまり、 delete a 後の a の値は、以下の注意点を除いて、 a が代入なしで宣言された場合と同じになります。

delete はマッピングには影響を与えません(マッピングのキーは任意である可能性があり、一般的には不明であるため)。 そのため、構造体を削除すると、マッピングではないすべてのメンバーがリセットされ、またマッピングでない限り、そのメンバーに再帰します。 しかし、個々のキーとそれが何にマッピングされているかは削除できます。 a がマッピングであれば、 delete a[x]x に格納されている値を削除します。

delete a は実際には a への代入のように振る舞います。 つまり、 a に新しいオブジェクトを格納するということに注意することが重要です。 この違いは、 a が参照変数の場合に見られます。 delete aa 自体をリセットするだけで、以前参照していた値はリセットしません。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract DeleteExample {
    uint data;
    uint[] dataArray;

    function f() public {
        uint x = data;
        delete x; // xを0にセットし、dataには影響を与えない
        delete data; // dataを0にセットし、xには影響を与えない
        uint[] storage y = dataArray;
        delete dataArray; // これは dataArray.length を 0 にするものですが、
        // uint[] は複合オブジェクトであるため、ストレージオブジェクトのエイリアスである y にも影響が及びます。
        // ストレージオブジェクトを参照するローカル変数への代入は、既存のストレージオブジェクトからしか行えないため、"delete y"は有効ではありません。
        assert(y.length == 0);
    }
}

演算子の優先順位

演算子の優先順位を評価順に並べると以下のようになります。

優先順位

説明

演算子

1

後置インクリメントとデクリメント

++, --

New式

new <typename>

配列添字アクセス

<array>[<index>]

メンバアクセス

<object>.<member>

関数的呼び出し

<func>(<args...>)

括弧

(<statement>)

2

前置インクリメントとデクリメント

++, --

単項マイナス

-

単項演算子

delete

論理否定

!

ビット単位の否定

~

3

累乗

**

4

乗算、除算、剰余

*, /, %

5

加算、減算

+, -

6

ビット単位のシフト演算

<<, >>

7

ビット単位のAND

&

8

ビット単位のXOR

^

9

ビット単位のOR

|

10

不等号演算子

<, >, <=, >=

11

等号演算子

==, !=

12

論理AND

&&

13

論理OR

||

14

三項演算子

<conditional> ? <if-true> : <if-false>

代入演算子

=, |=, ^=, &=, <<=, >>=, +=, -=, *=, /=, %=

15

カンマ演算子

,

初等型間の変換

暗黙的な変換

暗黙の型変換は、代入時、関数に引数を渡すとき、および演算子を適用するときに、コンパイラによって自動的に適用される場合があります。 一般に、セマンティック的に正しく、情報が失われないのであれば、値型間の暗黙の変換は可能です。

例えば、 uint8uint16 に、 int128int256 に変換できますが、 uint256-1 のような値を保持できないため、 int8uint256 に変換できません。

演算子が異なる型に適用される場合、コンパイラはオペランドの一方を他方の型に暗黙のうちに変換しようとします(代入の場合も同様)。 つまり、演算は常に一方のオペランドの型で実行されることになります。

どのような暗黙の変換が可能であるかの詳細については、型自体に関するセクションを参照してください。

下の例では、加算のオペランドである yz は同じ型ではありませんが、 uint8 は暗黙のうちに uint16 に変換でき、その逆はできません。 そのため、 yz の型に変換されてから、 uint16 の型で加算が行われます。 その結果、式 y + z の型は uint16 となります。 uint32 型の変数に代入されているため、加算の後に別の暗黙の変換が行われます。

uint8 y;
uint16 z;
uint32 x = y + z;

明示的な変換

コンパイラが暗黙的な変換を許可していないが、変換がうまくいくと確信している場合、明示的な型変換が可能な場合があります。 この場合、予期しない動作をしたり、コンパイラのセキュリティ機能を迂回したりすることがありますので、結果が期待通りのものであることを必ずテストしてください。

次の例では、マイナスの intuint に変換しています。

int  y = -3;
uint x = uint(y);

このコードスニペットの最後に、 x は256ビットの2の補数表現で-3である 0xfffff..fd (64の16進文字)という値を持つことになります。

整数を明示的に小さい型に変換すると、高次のビットが削られます。

uint32 a = 0x12345678;
uint16 b = uint16(a); // b は 0x5678 になる

整数がより大きな型に明示的に変換された場合、その整数は左に(すなわち高次の端に)パディングされます。 変換の結果は、元の整数と同じになります。

uint16 a = 0x1234;
uint32 b = uint32(a); // b は 0x00001234 になる
assert(a == b);

固定サイズのバイト、変換時の挙動が異なります。 これらは個々のバイトのシーケンスと考えることができ、より小さな型に変換するとシーケンスが切断されます。

bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b は 0x12 になる

固定サイズのバイト型が明示的に大きな型に変換された場合は、右にパディングされます。 固定インデックスでバイトにアクセスすると、変換前と変換後で同じ値になります(インデックスがまだ範囲内にある場合)。

bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b は 0x12340000 になる
assert(a[0] == b[0]);
assert(a[1] == b[1]);

整数と固定サイズのバイト配列は、切り捨てやパディングの際に異なる動作をするので、 整数と固定サイズのバイト配列の間の明示的な変換は、両者が同じサイズである場合にのみ許されます。 異なるサイズの整数と固定サイズのバイト配列の間で変換したい場合は、必要な切り捨てとパディングの規則を明示する中間変換を使用しなければなりません。

bytes2 a = 0x1234;
uint32 b = uint16(a); // b は 0x00001234 になる
uint32 c = uint32(bytes4(a)); // c は 0x12340000 になる
uint8 d = uint8(uint16(a)); // d は 0x34 になる
uint8 e = uint8(bytes1(a)); // e は 0x12 になる

bytes 配列と bytes calldata sliceは、明示的に固定バイト型( bytes1 / ... / bytes32 )に変換できます。 配列が対象となる固定バイト型よりも長い場合は、末尾の切り捨てが行われます。 配列が対象となる固定バイト型よりも短い場合は、末尾にゼロが詰められます。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;

contract C {
    bytes s = "abcdefgh";
    function f(bytes calldata c, bytes memory m) public view returns (bytes16, bytes3) {
        require(c.length == 16, "");
        bytes16 b = bytes16(m);  // m の長さが 16 より大きい場合、切り捨てが発生します。
        b = bytes16(s);  // 右詰めしたもので、結果は "abcdefgh\0\0\0\0\0\0\0\0"
        bytes3 b1 = bytes3(s); //切り捨て、b1は"abc"に等しい。
        b = bytes16(c[:8]);  // ゼロ埋めされる
        return (b, b1);
    }
}

リテラルと初等型間の変換

整数型

10進数や16進数のリテラルは、切り捨てずに表現できる大きさの整数型に暗黙のうちに変換できます。

uint8 a = 12; // OK
uint32 b = 1234; // OK
uint16 c = 0x123456; // 失敗、0x3456に切り捨てなければならないため。

注釈

バージョン0.8.0以前では、10進数や16進数のリテラルを明示的に整数型に変換できました。 0.8.0からは、このような明示的な変換は暗黙的な変換と同様に厳格になりました。

固定サイズバイト列

10進数リテラルを固定サイズのバイト列に暗黙的に変換できません。 16進数リテラルは変換できますが、それは16進数の桁数がバイト型のサイズにぴったり合う場合に限られます。 例外として、0の値を持つ10進数リテラルと16進数リテラルは、任意の固定サイズのバイト型に変換できます。

bytes2 a = 54321; // NG
bytes2 b = 0x12; // NG
bytes2 c = 0x123; // NG
bytes2 d = 0x1234; // OK
bytes2 e = 0x0012; // OK
bytes4 f = 0; // OK
bytes4 g = 0x0; // OK

文字列リテラルと16進文字列リテラルは、その文字数がバイト型のサイズと一致する場合、暗黙のうちに固定サイズのバイト配列に変換できます。

bytes2 a = hex"1234"; // OK
bytes2 b = "xy"; // OK
bytes2 c = hex"12"; // NG
bytes2 d = hex"123"; // NG
bytes2 e = "x"; // NG
bytes2 f = "xyz"; // NG

アドレス

アドレスリテラル で説明したように、チェックサムテストに合格した正しいサイズの16進数リテラルは address 型となります。 他のリテラルは暗黙的に address 型に変換できません。

明示的に address に変換できるのは bytes20uint160 だけです。

address apayable(a) によって address payable に明示的に変換できます。

注釈

バージョン0.8.0以前では、任意の整数型(符号あり、符号なし、サイズは問わない)から address または address payable への明示的な変換が可能でした。 0.8.0からは uint160 からの変換のみが可能になりました。