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
の別名でした。
新しい制約
このセクションでは、既存のコントラクトのコンパイルができなくなる可能性のある変更点を示します。
リテラルの明示的な変換に関連する新しい制限があります。 以下のようなケースでの従来の動作は、曖昧であったと思われます。
負のリテラルや
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
、標準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
はゼロではないことを確認してください)。