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より多くの型をサポートし、入力に対してより厳密なサニティチェックを行います。 そのため一部の関数呼び出しはコストが高くなり、またパラメータ型に適合しないデータを含む場合、v1ではリバートしなかったコントラクト呼び出しがリバートすることがあります。
累乗は右結合です。つまり、
a**b**cという式はa**(b**c)として解析されます。 0.8.0以前は(a**b)**cと解析されていました。これは、指数演算子を解析する一般的な方法です。
ゼロ除算や算術オーバーフローなどの失敗したアサーションやその他の内部チェックは、invalid opcodeではなくrevert opcodeを使用します。 より具体的には、状況に応じたエラーコードを持つ
Panic(uint256)への関数呼び出しと同一のエラーデータを返します。これによりエラー時のガスが節約され、静的解析ツールはこれらの状況を
requireの失敗など無効な入力に対するリバートと区別できます。
ストレージのバイト配列の長さが正しくエンコードされていないものにアクセスすると、パニックが発生します。 コントラクトは、ストレージのバイト配列の生の表現を変更するためにインラインアセンブリを使用しない限り、このような状況に陥ることはありません。
定数を配列の長さの式で使用する場合、以前のバージョンのSolidityでは、評価ツリーのすべての分岐で任意の精度を使用していました。 現在では、定数変数が中間式として使用されている場合、その値はランタイム式で使用されている場合と同様に適切に丸められます。
byte型は削除されました。 これはbytes1の別名でした。
新しい制約
このセクションでは、既存のコントラクトのコンパイルができなくなる可能性のある変更点を示します。
リテラルの明示的な変換に関連する新しい制限があります。 以下のようなケースでの従来の動作は、曖昧であったと思われます。
負のリテラルや
type(uint160).maxより大きいリテラルからaddressへの明示的な変換は禁止されています。リテラルと整数型
Tの間の明示的な変換は、リテラルがtype(T).minとtype(T).maxの間にある場合にのみ許されます。 特に、uint(-1)の使用をtype(uint).maxに置き換えてください。リテラルと列挙型の間の明示的な変換は、リテラルが列挙型の値を表すことができる場合にのみ許可されます。
リテラルと
address型の間の明示的な変換(例:address(literal))は、address payableの代わりにaddress型を持ちます。 明示的な変換を使用することで、payableなアドレス型を得ることができます。 すなわち、payable(literal)です。
アドレスリテラル は、
address payableの代わりにaddressという型を持っています。 これらは、payable(0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF)のような明示的な変換を用いることで、address payableに変換できます。
明示的な型変換には新しい制限があります。 変換は、符号、幅、または型カテゴリ(
int、address、bytesNNなど)に最大1つの変更がある場合にのみ許可されます。 複数の変更を行うには、複数の変換を使用します。ここで、
TとSは型であり、xはS型の任意の変数である、明示的な変換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}()に変更しなければなりません。
グローバル関数の
log0、log1、log2、log3、log4が削除されました。これらは、ほとんど使われていない低レベルの関数です。 これらの動作はインラインアセンブリからアクセスできます。
enum定義は256個以上のメンバーを含むことはできません。これにより、ABIの基礎となる型が常に
uint8であると仮定しても安全になります。
this、super、_という名前の宣言は、パブリック関数とイベントを除いて禁止されています。 この例外は、Solidity以外の言語で実装されたコントラクトのインターフェースを宣言できるようにするためのもので、このような関数名を許可しています。
コード内の
\b、\f、\vのエスケープシーケンスのサポートを削除しました。 これらは、それぞれ\x08、\x0c、\x0bなどの16進数のエスケープで挿入できます。
グローバル変数
tx.originとmsg.senderの型は、address payableではなくaddressです。 これらをaddress payableに変換するには、明示的な変換を、すなわちpayable(tx.origin)またはpayable(msg.sender)を用いてください。この変更は、これらのアドレスがpayableかどうかをコンパイラが判断できないため、この要件を可視化するために明示的な変換を必要とするようになりました。
address型への明示的な変換は、常に支払い不可能なaddress型を返します。 特に、以下の明示的な変換は、address payable型ではなくaddress型になります。address(u)ここで、uはuint160型の変数です。uをaddress payable型に変換するには、2つの明示的な変換、すなわちpayable(address(u))を用いてください。address(b)ここで、bはbytes20型の変数です。bをaddress payable型に変換するには、2つの明示的な変換、すなわちpayable(address(b))を用いてください。address(c)(cはコントラクト)。 以前は、この変換のリターン型は、コントラクトがEtherを受信できるかどうかに依存していました(受信関数または支払可能なフォールバック関数を持つことにより)。payable(c)変換はaddress payable型で、コントラクトcがEtherを受け取ることができる場合にのみ許可されます。 一般的には、次の明示的な変換を用いることで、常にcをaddress payable型に変換できます:payable(address(c))。address(this)は、address(c)と同じカテゴリーに属し、同じルールが適用されることに注意してください。
インラインアセンブリの
chainidビルトインは、pureではなくviewとみなされるようになりました。
単項否定は符号なし整数では使用できなくなり、符号付き整数でのみ使用できるようになりました。
インターフェースの変更
--combined-jsonの出力が変わりました。 JSONのフィールドabi、devdoc、userdoc、storage-layoutがサブオブジェクトになりました。 0.8.0以前では、これらは文字列としてシリアライズされていました。「レガシーAST」が削除されました(コマンドラインインターフェースでは
--ast-json、スタンダードJSONではlegacyAST)。 この代わりに「コンパクトAST」(それぞれ--ast-compact-jsonとAST)を使用してください。旧エラーレポーター(
--old-reporter)は削除されました。
コードのアップデート方法
ラッピング算術(オーバーフローを許容する算術)に頼っている場合は、各演算を
unchecked { ... }で囲んでください。オプション: SafeMathまたは同様のライブラリを使用している場合は、
x.add(y)をx + y、x.mul(y)をx * yなどに変更してください。古いABIコーダーを使用したい場合は、
pragma abicoder v1;を追加してください。冗長なので、オプションで
pragma experimental ABIEncoderV2またはpragma abicoder v2を削除してください。byteをbytes1に変更してください。必要に応じて、中間の明示的な型変換を追加してください。
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はゼロではないことを確認してください)。