Solidity v0.8.0の破壊的変更点

このセクションでは、Solidityのバージョン0.8.0で導入された主な変更点を紹介します。 完全なリストは リリースチェンジログ を参照してください。

セマンティクスのサイレントな変更点

このセクションでは、既存のコードがコンパイラーに通知されることなく動作を変更する変更点を示します。

  • 算術演算は、アンダーフローとオーバーフローでリバートします。 unchecked { ... } を使えば、以前の折り返し動作を使うことができます。

    オーバーフローのチェックは非常に一般的なものなので、多少ガス代が高くなってもコードの可読性を高めるためにデフォルトにしました。

  • ABI coder v2はデフォルトで起動しています。

    pragma abicoder v1; を使って古い動作を選択できます。 プラグマ pragma experimental ABIEncoderV2; はまだ有効ですが、非推奨であり、効果はありません。 明示的にしたい場合は、代わりに pragma abicoder v2; を使用してください。

    ABI coder v2は、v1よりも多くの型をサポートし、入力に対してより多くのサニティチェックを行うことに注意してください。 ABI coder v2では、一部の関数呼び出しがより高価になり、また、パラメータの型に適合しないデータが含まれている場合、ABI coder v1ではリバートしなかったコントラクトコールがリバートすることがあります。

  • つまり、 a**b**c という式は a**(b**c) として解析されます。 0.8.0以前は (a**b)**c と解析されていました。

    これは、指数演算子を解析する一般的な方法です。

  • ゼロ除算や算術オーバーフローなどの失敗したアサーションやその他の内部チェックは、invalid opcodeではなくrevert opcodeを使用します。 より具体的には、状況に応じたエラーコードを持つ Panic(uint256) への関数呼び出しと等しいエラーデータを使用します。

    これにより、エラー時のガスを節約できますが、静的解析ツールでは、このような状況を、 require の失敗のような無効な入力に対するリバートと区別できます。

  • ストレージのバイト配列の長さが正しくエンコードされていないものにアクセスすると、パニックが発生します。 コントラクトは、ストレージのバイト配列の生の表現を変更するためにインラインアセンブリを使用しない限り、このような状況に陥ることはありません。

  • 定数を配列の長さの式で使用する場合、以前のバージョンのSolidityでは、評価ツリーのすべての分岐で任意の精度を使用していました。 現在では、定数変数が中間式として使用されている場合、その値はランタイム式で使用されている場合と同様に適切に丸められます。

  • byte 型は削除されました。 これは bytes1 の別名でした。

新しい制約

このセクションでは、既存のコントラクトのコンパイルができなくなる可能性のある変更点を示します。

  • リテラルの明示的な変換に関連する新しい制限があります。 以下のようなケースでの従来の動作は、曖昧であったと思われます。

    1. 負のリテラルや type(uint160).max より大きいリテラルから address への明示的な変換は禁止されています。

    2. リテラルと整数型 T の間の明示的な変換は、リテラルが type(T).mintype(T).max の間にある場合にのみ許されます。 特に、 uint(-1) の使用を type(uint).max に置き換えてください。

    3. リテラルと列挙型の間の明示的な変換は、リテラルが列挙型の値を表すことができる場合にのみ許可されます。

    4. リテラルと address 型の間の明示的な変換(例: address(literal) )は、 address payable の代わりに address 型を持ちます。 明示的な変換を使用することで、payableなアドレス型を得ることができます。 すなわち、 payable(literal) です。

  • アドレスリテラル は、 address payable の代わりに address という型を持っています。 これらは、 payable(0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF) のような明示的な変換を用いることで、 address payable に変換できます。

  • 明示的な型変換には新しい制限があります。 変換は、符号、幅、または型カテゴリ( intaddressbytesNN など)に最大1つの変更がある場合にのみ許可されます。 複数の変更を行うには、複数の変換を使用します。

    ここで、 TS は型であり、 xS 型の任意の変数である、明示的な変換 T(x) を表すために T(S) という表記を使用してみましょう。 このような許されない変換の例としては、 uint16(int8) があります。 uint16(int8) は幅(8ビットから16ビット)と符号(符号付き整数から符号なし整数)の両方を変更するからです。 変換を行うためには、中間型を経由しなければなりません。 先ほどの例では、 uint16(uint8(int8)) または uint16(int16(int8)) となります。 この2つの変換方法では、例えば -1 の場合、異なる結果が得られることに注意してください。 以下は、この規則によって許されない変換の例です。

    • address(uint)uint(address): 型カテゴリーと幅の両方を変換します。 これはそれぞれ address(uint160(uint))uint(uint160(address)) に置き換えてください。

    • payable(uint160) , payable(bytes20), payable(integer-literal): 型カテゴリとステートミュータビリティの両方を変換します。 これはそれぞれ payable(address(uint160)) , payable(address(bytes20)) , payable(address(integer-literal)) に置き換えるてください。 なお、 payable(0) は有効であり、例外です。

    • int80(bytes10)bytes10(int80): 型カテゴリーと符号の両方を変換します。 これはそれぞれ int80(uint80(bytes10))bytes10(uint80(int80) に置き換えてください。

    • Contract(uint): 型カテゴリと幅の両方を変換しています。 これは Contract(address(uint160(uint))) に置き換えてください。

    これらの変換は、曖昧さを避けるために認められませんでした。 例えば、 uint16 x =   uint16(int8(-1)) という表現では、 x の値は、符号と幅のどちらの変換が最初に適用されるかに依存します。

  • 関数呼び出しのオプションは一度しか与えることができません。 つまり、 c.f{gas: 10000}{value: 1}() は無効で、 c.f{gas: 10000, value: 1}() に変更しなければなりません。

  • グローバル関数の log0log1log2log3log4 が削除されました。

    これらは、ほとんど使われていない低レベルの関数です。 これらの動作はインラインアセンブリからアクセスできます。

  • enum 定義は256個以上のメンバーを含むことはできません。

    これにより、ABIの基礎となる型が常に uint8 であると仮定しても安全になります。

  • thissuper_ という名前の宣言は、パブリック関数とイベントを除いて禁止されています。 この例外は、Solidity以外の言語で実装されたコントラクトのインターフェースを宣言できるようにするためのもので、このような関数名を許可しています。

  • コード内の \b\f\v のエスケープシーケンスのサポートを削除しました。 これらは、それぞれ \x08\x0c\x0b などの16進数のエスケープで挿入できます。

  • グローバル変数 tx.originmsg.sender の型は、 address payable ではなく address です。 これらを address payable に変換するには、明示的な変換を、すなわち payable(tx.origin) または payable(msg.sender) を用いてください。

    この変更は、これらのアドレスがpayableかどうかをコンパイラが判断できないため、この要件を可視化するために明示的な変換を必要とするようになりました。

  • address 型への明示的な変換は、常に支払い不可能な address 型を返します。 特に、以下の明示的な変換は、 address payable 型ではなく address 型になります。

    • address(u) ここで、 uuint160 型の変数です。 uaddress payable 型に変換するには、2つの明示的な変換、すなわち payable(address(u)) を用いてください。

    • address(b) ここで、 bbytes20 型の変数です。 baddress payable 型に変換するには、2つの明示的な変換、すなわち payable(address(b)) を用いてください。

    • address(c)c はコントラクト)。 以前は、この変換のリターン型は、コントラクトがEtherを受信できるかどうかに依存していました(受信関数または支払可能なフォールバック関数を持つことにより)。 payable(c) 変換は address payable 型で、コントラクト c がEtherを受け取ることができる場合にのみ許可されます。 一般的には、次の明示的な変換を用いることで、常に caddress payable 型に変換できます: payable(address(c))address(this) は、 address(c) と同じカテゴリーに属し、同じルールが適用されることに注意してください。

  • インラインアセンブリの chainid ビルトインは、 pure ではなく view とみなされるようになりました。

  • 単項否定は符号なし整数では使用できなくなり、符号付き整数でのみ使用できるようになりました。

インターフェースの変更

  • --combined-json の出力が変わりました。 JSONのフィールド abidevdocuserdocstorage-layout がサブオブジェクトになりました。 0.8.0以前では、これらは文字列としてシリアライズされていました。

  • 「レガシーAST」が削除されました(コマンドラインインターフェースでは --ast-json 、標準JSONでは legacyAST )。 代わりに「コンパクトAST」( --ast-compact--json 、標準JSONでは AST )を使用してください。

  • 旧エラーレポーター( --old-reporter )は削除されました。

コードのアップデート方法

  • ラッピング算術(オーバーフローを許容する算術)に頼っている場合は、各演算を unchecked { ... } で囲んでください。

  • オプション: SafeMathまたは同様のライブラリを使用している場合は、 x.add(y)x + yx.mul(y)x * y などに変更してください。

  • 古いABIコーダーを使用したい場合は、 pragma abicoder v1; を追加してください。

  • 冗長なので、オプションで pragma experimental ABIEncoderV2 または pragma abicoder v2 を削除してください。

  • bytebytes1 に変更してください。

  • 必要に応じて、中間の明示的な型変換を追加してください。

  • c.f{gas: 10000}{value: 1}()c.f{gas: 10000, value: 1}() に結合してください。

  • msg.sender.transfer(x)payable(msg.sender).transfer(x) に変更するか、 address payable 型のstored変数を使用してください。

  • x**y**z(x**y)**z に変更してください。

  • log0 、...、 log4 の代わりにインラインアセンブリを使用してください。

  • 符号なし整数を、その型の最大値から引いて1を加えて否定してください(例: type(uint256).max - x + 1 、ただし x はゼロではないことを確認してください)。