Solidity v0.5.0の破壊的変更点

このセクションでは、Solidityバージョン0.5.0で導入された主な変更点と、変更の理由、影響を受けるコードの更新方法について説明します。 完全なリストは リリースのチェンジログ を参照してください。

注釈

Solidity v0.5.0でコンパイルされたコントラクトは、古いバージョンでコンパイルされたコントラクトやライブラリを再コンパイルや再配置することなく、それらとインターフェースをとることができます。 データの場所やビジビリティとミュータビリティの指定子を含むようにインターフェースを変更すれば十分です。 以下の 従来のコントラクトとの相互運用性 セクションを参照してください。

セマンティックのみの変更点

このセクションでは、セマンティックのみの変更点をリストアップしています。 そのため、既存のコードの中に新しい、あるいは異なる動作が隠されている可能性があります。

  • 符号付き右シフトでは、ゼロに丸めるのではなく、負の無限大に丸めるなど、適切な算術的シフトが使用されるようになりました。 符号付きと符号なしのシフトは、Constantinopleでは専用のオペコードが用意されていますが、現時点ではSolidityでエミュレートされています。

  • do...while ループ内の continue 文は、条件にジャンプするようになりましたが、これはこのような場合の一般的な動作です。 以前は、ループ本体にジャンプしていました。 したがって、条件が偽の場合、ループは終了します。

  • 関数 .call().delegatecall().staticcall() は、単一の bytes パラメータが与えられた場合、パッドがなくなります。

  • EVMのバージョンがByzantium以降の場合、PureおよびView関数は CALL ではなく STATICCALL というオペコードで呼び出されるようになりました。 これにより、EVMレベルでの状態変更ができなくなります。

  • ABIエンコーダーは、外部関数呼び出しや abi.encode で使用される calldata ( msg.data および外部関数パラメーター)からのバイト配列や文字列を適切にパッドするようになりました。 パッドされていないエンコーディングには、 abi.encodePacked を使用してください。

  • ABIデコーダは、関数の先頭や abi.decode() で、渡されたcalldataが短すぎたり、境界外を指したりした場合には、リバートします。 なお、ダーティな高次ビットはまだ単純に無視されます。

  • Tangerine Whistleから始まる外部関数呼び出しで、利用可能なすべてのガスを転送します。

セマンティックかつシンタックスの変更点

このセクションでは、シンタックスとセマンティックに影響する変更点を紹介します。

  • 関数 .call().delegatecall()staticcall()keccak256()sha256()ripemd160() は、 bytes の引数を1つだけ受け付けるようになりました。 さらに、この引数はパディングされません。 これは、引数がどのように連結されるかをより明示的かつ明確にするために変更されました。 すべての .call() (およびファミリー)を .call("") に、すべての .call(signature, a, b, c).call(abi.encodeWithSignature(signature, a, b, c)) に変更しました(最後のものはvalue型でのみ機能します)。 すべての keccak256(a, b, c)keccak256(abi.encodePacked(a, b, c)) に変更しました。 壊すような変更ではありませんが、開発者は x.call(bytes4(keccak256("f(uint256)")), a, b)x.call(abi.encodeWithSignature("f(uint256)", a, b)) に変更することを提案します。

  • 関数 .call().delegatecall().staticcall()(bool, bytes memory) を返すようになり、戻りデータへのアクセスが可能になりました。 bool success = otherContract.call("f")(bool success, bytes memory data) = otherContract.call("f") に変更します。

  • Solidityは、関数のローカル変数にC99スタイルのスコープルールを実装しました。 つまり、変数は宣言された後にのみ使用でき、同じスコープまたはネストされたスコープ内でのみ使用できます。 for ループの初期化ブロックで宣言された変数は、ループ内のどの時点でも有効です。

明示的な要件

このセクションでは、コードをより明確にする必要がある変更点を示します。 ほとんどの項目では、コンパイラが提案をしてくれます。

  • 関数の明示的な可視化が必須になりました。 すべての関数とコンストラクタに public を追加し、ビジビリティを指定していないすべてのフォールバック関数やインターフェース関数に external を追加します。

  • 構造体(struct)、配列(array)、マッピング(mapping)型のすべての変数について、明示的なデータ配置が必須となりました。 これは、関数のパラメータやリターン変数にも適用されます。 例えば、 uint[] x = zuint[] storage x = z に、 function f(uint[][] x)function f(uint[][] memory x) に変更すると、 memory がデータロケーションとなり、 storagecalldata に適宜置き換えられます。 なお、 external 関数ではデータロケーションが calldata のパラメータが必要です。

  • 名前空間を分離するために、コントラクト型には address メンバーが含まれなくなりました。 そのため、 address メンバを使用する前に、コントラクト型の値を明示的にアドレスに変換する必要があります。 例: c がコントラクトの場合、 c.transfer(...)address(c).transfer(...) に、 c.balanceaddress(c).balance に変更します。

  • 関連性のないコントラクト型間の明示的な変換ができなくなりました。 あるコントラクト型から、そのベースまたは祖先の型の1つへの変換のみが可能です。 あるコントラクトが、変換したいコントラクト型を継承していないものの、互換性があると確信している場合、最初に address に変換することでこれを回避できます。 例: AB がコントラクト型で、 BA から継承されず、 bB 型のコントラクトである場合、 A(address(b)) を使って bA 型に変換できます。 なお、以下に説明するように、マッチングで支払い可能なフォールバック関数にも注意する必要があります。

  • address 型は addressaddress payable に分割され、 address payable のみが transfer 関数を提供しています。 address payable を直接 address に変換できますが、その逆はできません。 address から address payable への変換は、 uint160 による変換で可能です。 c がコントラクトの場合、 address(c) は、 c に支払い可能なフォールバック関数がある場合に限り、 address payable になります。 withdraw pattern を使用している場合、 transfer はストアドアドレスではなく msg.sender でのみ使用され、 msg.senderaddress payable になるので、コードを変更する必要はほとんどありません。

  • 異なるサイズの bytesXuintY の間の変換は、 bytesX のパディングが右に、 uintY のパディングが左にあるため、予期しない変換結果を引き起こす可能性があるため、許可されなくなりました。 変換の前に、型内でサイズを調整する必要があるようになりました。 例えば、 bytes4 (4バイト)を uint64 (8バイト)に変換するには、まず bytes4 の変数を bytes8 に変換し、次に uint64 に変換します。 uint32 で変換すると逆にパディングされてしまいます。 v0.5.0以前のバージョンでは、 bytesXuintY の間の変換は uint8X を経由します。 例えば、 uint8(bytes3(0x291807))uint8(uint24(bytes3(0x291807))) に変換されます(結果は 0x07 )。

  • 払えない関数で msg.value を使う(またはモディファイアで導入する)ことは、セキュリティ機能として認められていません。 関数を payable に変えるか、 msg.value を使用するプログラムロジックのために新しい内部関数を作成してください。

  • わかりやすくするために、コマンドラインインターフェースでは、標準入力をソースとして使用する場合、 - を要求するようになりました。

非推奨の要素

このセクションでは、以前の機能や構文を廃止する変更点を紹介します。 これらの変更点の多くは、実験モードの v0.5.0 ですでに有効になっていることに注意してください。

コマンドラインインターフェースとJSONインターフェース

  • コマンドラインオプションの --formal (さらなる形式検証のためにWhy3出力を生成するために使用)は非推奨であり、現在は削除されています。 新しいフォーマル検証モジュールであるSMTCheckerは、 pragma experimental SMTChecker; を介して有効になります。

  • 中間言語 JuliaYul に名称変更されたことに伴い、コマンドラインオプション --julia--yul に名称変更されました。

  • --clone-bin および --combined-json clone-bin コマンドラインオプションが削除されました。

  • 空のプレフィックスを持つリマッピングは許可されません。

  • JSON ASTフィールドの constantpayable が削除されました。 情報は stateMutability フィールドに存在するようになりました。

  • FunctionDefinition ノードのJSON ASTフィールド isConstructor が、 "constructor""fallback""function" の値を持つことができる kind というフィールドに置き換えられました。

  • リンクされていないバイナリ16進数ファイルでは、ライブラリアドレスのプレースホルダーが、完全修飾ライブラリ名のkeccak256ハッシュの最初の3616文字を $...$ で囲んだものになりました。 以前は、完全修飾ライブラリ名のみが使用されていました。 これにより、特に長いパスを使用している場合に、衝突の可能性が低くなります。 バイナリファイルには、これらのプレースホルダーから完全修飾名へのマッピングのリストも含まれるようになりました。

コンストラクタ

  • コンストラクタは、 constructor キーワードを使って定義する必要があります。

  • ベースコンストラクタを括弧なしで呼び出すことができなくなりました。

  • ベースコンストラクタの引数を同じ継承階層で複数回指定できなくなりました。

  • 引数を持つコンストラクタを、間違った引数数で呼び出すことはできなくなりました。 引数を与えずに継承関係だけを指定したい場合は、括弧を一切付けないでください。

関数

  • callcode 関数は、現在では使用できません( delegatecall に変更)。 ただし、インラインアセンブリで使用することは可能です。

  • suicide は( selfdestruct を優先して)不許可になりました。

  • sha3 は( keccak256 を優先して)不許可になりました。

  • throw は現在、( revertrequireassert に代わって)不許可となっています。

変換

  • 10進数のリテラルから bytesXX 型への明示的、暗黙的な変換ができなくなりました。

  • 16進数のリテラルから異なるサイズの bytesXX 型への明示的および暗黙的な変換ができなくなりました。

リテラルと接尾辞

  • 単位表記の years は、うるう年の複雑さと混乱のため、現在は認められていません。

  • 数字を含まない末尾のドットは使用できません。

  • 16進数と単位表記(例: 0x1e wei )の組み合わせができなくなりました。

  • 16進数の接頭辞 0X は使用できず、 0x のみ使用可能です。

変数

  • 空の構造体を宣言することは、わかりやすくするために禁止されました。

  • var キーワードを使用しないようにしたことで、明示性が確保されました。

  • コンポーネントの数が異なるタプル間の割り当てができなくなりました。

  • コンパイル時の定数ではない定数の値は許されません。

  • 値の数が不一致の複数変数の宣言ができなくなりました。

  • 初期化されていないストレージ変数が禁止されるようになりました。

  • 空のタプル構成要素が許されなくなりました。

  • 変数や構造体の周期的な依存関係の検出は、再帰的に256に制限されます。

  • 長さがゼロの固定サイズの配列が禁止されるようになりました。

シンタックス

  • constant を関数のステートミュータビリティのモディファイアとして使用できなくなりました。

  • ブール式では、算術演算は使えません。

  • 単項の + 演算子が使えなくなりました。

  • リテラルは、事前に明示的な型に変換することなく、 abi.encodePacked で使用できなくなりました。

  • 1つ以上の戻り値を持つ関数の空の戻り文は認められなくなりました。

  • つまり、ジャンプラベルやジャンプ、機能しない命令はもう使用できません。 代わりに新しい whileswitchif 構文を使ってください。

  • 実装のない関数では、モディファイアが使えなくなりました。

  • 名前付きの戻り値を持つ関数型が禁止されるようになりました。

  • ブロックではないif/while/forボディ内の単一の文の変数宣言が禁止されました。

  • 新しいキーワードです。 calldataconstructor です。

  • 新しい予約キーワードです: alias , apply , auto , copyof , define , immutable , implements , macro , mutable , override , partial , promise , reference , sealed , sizeof , supports , typedef , unchecked

従来のコントラクトとの相互運用性

0.5.0より前のバージョンのSolidityで書かれたコントラクトにインターフェースを定義することで、コントラクトとインターフェースを結ぶことができます。 以下の0.5.0以前のコントラクトがすでにデプロイされているとします。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.25;
// This will report a warning until version 0.4.25 of the compiler
// This will not compile after 0.5.0
contract OldContract {
    function someOldFunction(uint8 a) {
        //...
    }
    function anotherOldFunction() constant returns (bool) {
        //...
    }
    // ...
}

これはSolidity v0.5.0ではコンパイルされなくなります。 ただし、互換性のあるインターフェースを定義することは可能です:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
interface OldContract {
    function someOldFunction(uint8 a) external;
    function anotherOldFunction() external returns (bool);
}

オリジナルのコントラクトでは constant と宣言されていたにもかかわらず、 anotherOldFunctionview と宣言していないことに注意してください。 これは、Solidity v0.5.0から view 関数のコールに staticcall が使われるようになったことによります。 v0.5.0以前は constant キーワードが強制されていなかったため、 constant と宣言された関数を staticcall で呼び出しても、 constant 関数がストレージを変更しようとする可能性があるため、リバートする可能性があります。 したがって、古いコントラクトのインターフェースを定義する際には、その関数が staticcall で動作することが絶対的に確認できる場合にのみ、 constant の代わりに view を使用する必要があります。

上記で定義されたインターフェースがあれば、すでにデプロイされたpre-0.5.0のコントラクトを簡単に使用できます。

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

interface OldContract {
    function someOldFunction(uint8 a) external;
    function anotherOldFunction() external returns (bool);
}

contract NewContract {
    function doSomething(OldContract a) public returns (bool) {
        a.someOldFunction(0x42);
        return a.anotherOldFunction();
    }
}

同様に、0.5.0以前のライブラリも、実装せずにライブラリの関数を定義し、リンク時に0.5.0以前のライブラリのアドレスを指定することで使用できます(リンク時のコマンドラインコンパイラの使用方法については コマンドラインコンパイラの使い方 をご参照ください)。

// This will not compile after 0.6.0
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.5.0;

library OldLibrary {
    function someFunction(uint8 a) public returns(bool);
}

contract NewContract {
    function f(uint8 a) public returns (bool) {
        return OldLibrary.someFunction(a);
    }
}

次の例は、Solidity v0.5.0のコントラクトとそのアップデート版で、このセクションに記載されている変更点があります。

古いバージョンです:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.25;
// This will not compile after 0.5.0

contract OtherContract {
    uint x;
    function f(uint y) external {
        x = y;
    }
    function() payable external {}
}

contract Old {
    OtherContract other;
    uint myNumber;

    // Function mutability not provided, not an error.
    function someInteger() internal returns (uint) { return 2; }

    // Function visibility not provided, not an error.
    // Function mutability not provided, not an error.
    function f(uint x) returns (bytes) {
        // Var is fine in this version.
        var z = someInteger();
        x += z;
        // Throw is fine in this version.
        if (x > 100)
            throw;
        bytes memory b = new bytes(x);
        y = -3 >> 1;
        // y == -1 (wrong, should be -2)
        do {
            x += 1;
            if (x > 10) continue;
            // 'Continue' causes an infinite loop.
        } while (x < 11);
        // Call returns only a Bool.
        bool success = address(other).call("f");
        if (!success)
            revert();
        else {
            // Local variables could be declared after their use.
            int y;
        }
        return b;
    }

    // No need for an explicit data location for 'arr'
    function g(uint[] arr, bytes8 x, OtherContract otherContract) public {
        otherContract.transfer(1 ether);

        // Since uint32 (4 bytes) is smaller than bytes8 (8 bytes),
        // the first 4 bytes of x will be lost. This might lead to
        // unexpected behavior since bytesX are right padded.
        uint32 y = uint32(x);
        myNumber += y + msg.value;
    }
}

新バージョンです:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.5.0;
// This will not compile after 0.6.0

contract OtherContract {
    uint x;
    function f(uint y) external {
        x = y;
    }
    function() payable external {}
}

contract New {
    OtherContract other;
    uint myNumber;

    // Function mutability must be specified.
    function someInteger() internal pure returns (uint) { return 2; }

    // Function visibility must be specified.
    // Function mutability must be specified.
    function f(uint x) public returns (bytes memory) {
        // The type must now be explicitly given.
        uint z = someInteger();
        x += z;
        // Throw is now disallowed.
        require(x <= 100);
        int y = -3 >> 1;
        require(y == -2);
        do {
            x += 1;
            if (x > 10) continue;
            // 'Continue' jumps to the condition below.
        } while (x < 11);

        // Call returns (bool, bytes).
        // Data location must be specified.
        (bool success, bytes memory data) = address(other).call("f");
        if (!success)
            revert();
        return data;
    }

    using AddressMakePayable for address;
    // Data location for 'arr' must be specified
    function g(uint[] memory /* arr */, bytes8 x, OtherContract otherContract, address unknownContract) public payable {
        // 'otherContract.transfer' is not provided.
        // Since the code of 'OtherContract' is known and has the fallback
        // function, address(otherContract) has type 'address payable'.
        address(otherContract).transfer(1 ether);

        // 'unknownContract.transfer' is not provided.
        // 'address(unknownContract).transfer' is not provided
        // since 'address(unknownContract)' is not 'address payable'.
        // If the function takes an 'address' which you want to send
        // funds to, you can convert it to 'address payable' via 'uint160'.
        // Note: This is not recommended and the explicit type
        // 'address payable' should be used whenever possible.
        // To increase clarity, we suggest the use of a library for
        // the conversion (provided after the contract in this example).
        address payable addr = unknownContract.makePayable();
        require(addr.send(1 ether));

        // Since uint32 (4 bytes) is smaller than bytes8 (8 bytes),
        // the conversion is not allowed.
        // We need to convert to a common size first:
        bytes4 x4 = bytes4(x); // Padding happens on the right
        uint32 y = uint32(x4); // Conversion is consistent
        // 'msg.value' cannot be used in a 'non-payable' function.
        // We need to make the function payable
        myNumber += y + msg.value;
    }
}

// We can define a library for explicitly converting ``address``
// to ``address payable`` as a workaround.
library AddressMakePayable {
    function makePayable(address x) internal pure returns (address payable) {
        return address(uint160(x));
    }
}