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 = zをuint[] storage x = zに、function f(uint[][] x)をfunction f(uint[][] memory x)に変更すると、memoryがデータロケーションとなり、storageやcalldataに適宜置き換えられます。 なお、external関数ではデータロケーションがcalldataのパラメータが必要です。
名前空間を分離するために、コントラクト型には
addressメンバーが含まれなくなりました。 そのため、addressメンバを使用する前に、コントラクト型の値を明示的にアドレスに変換する必要があります。 例:cがコントラクトの場合、c.transfer(...)をaddress(c).transfer(...)に、c.balanceをaddress(c).balanceに変更します。
関連性のないコントラクト型間の明示的な変換ができなくなりました。 あるコントラクト型から、そのベースまたは祖先の型の1つへの変換のみが可能です。 あるコントラクトが、変換したいコントラクト型を継承していないものの、互換性があると確信している場合、最初に
addressに変換することでこれを回避できます。 例:AとBがコントラクト型で、BはAから継承されず、bはB型のコントラクトである場合、A(address(b))を使ってbをA型に変換できます。 なお、以下に説明するように、マッチングで支払い可能なフォールバック関数にも注意する必要があります。
address型はaddressとaddress payableに分割され、address payableのみがtransfer関数を提供しています。address payableを直接addressに変換できますが、その逆はできません。addressからaddress payableへの変換は、uint160による変換で可能です。cがコントラクトの場合、address(c)は、cに支払い可能なフォールバック関数がある場合に限り、address payableになります。 withdraw pattern を使用している場合、transferはストアドアドレスではなくmsg.senderでのみ使用され、msg.senderはaddress payableになるので、コードを変更する必要はほとんどありません。
異なるサイズの
bytesXとuintYの間の変換は、bytesXのパディングが右に、uintYのパディングが左にあるため、予期しない変換結果を引き起こす可能性があるため、許可されなくなりました。 変換の前に、型内でサイズを調整する必要があるようになりました。 例えば、bytes4(4バイト)をuint64(8バイト)に変換するには、まずbytes4の変数をbytes8に変換し、次にuint64に変換します。uint32で変換すると逆にパディングされてしまいます。 v0.5.0以前のバージョンでは、bytesXとuintYの間の変換はuint8Xを経由します。 例えば、uint8(bytes3(0x291807))はuint8(uint24(bytes3(0x291807)))に変換されます(結果は0x07)。
払えない関数で
msg.valueを使う(またはモディファイアで導入する)ことは、セキュリティ機能として認められていません。 関数をpayableに変えるか、msg.valueを使用するプログラムロジックのために新しい内部関数を作成してください。
わかりやすくするために、コマンドラインインターフェースでは、標準入力をソースとして使用する場合、
-を要求するようになりました。
非推奨の要素
このセクションでは、以前の機能や構文を廃止する変更点を紹介します。
これらの変更点の多くは、実験モードの v0.5.0 ですでに有効になっていることに注意してください。
コマンドラインインターフェースとJSONインターフェース
コマンドラインオプションの
--formal(さらなる形式検証のためにWhy3出力を生成するために使用)は非推奨であり、現在は削除されています。 新しいフォーマル検証モジュールであるSMTCheckerは、pragma experimental SMTChecker;を介して有効になります。
中間言語
JuliaがYulに名称変更されたことに伴い、コマンドラインオプション--juliaが--yulに名称変更されました。
--clone-binおよび--combined-json clone-binコマンドラインオプションが削除されました。
空のプレフィックスを持つリマッピングは許可されません。
JSON ASTフィールドの
constantとpayableが削除されました。 情報はstateMutabilityフィールドに存在するようになりました。
FunctionDefinitionノードのJSON ASTフィールドisConstructorが、"constructor"、"fallback"、"function"の値を持つことができるkindというフィールドに置き換えられました。
リンクされていないバイナリ16進数ファイルでは、ライブラリアドレスのプレースホルダーが、完全修飾ライブラリ名のkeccak256ハッシュの最初の3616文字を
$...$で囲んだものになりました。 以前は、完全修飾ライブラリ名のみが使用されていました。 これにより、特に長いパスを使用している場合に、衝突の可能性が低くなります。 バイナリファイルには、これらのプレースホルダーから完全修飾名へのマッピングのリストも含まれるようになりました。
コンストラクタ
コンストラクタは、
constructorキーワードを使って定義する必要があります。
ベースコンストラクタを括弧なしで呼び出すことができなくなりました。
ベースコンストラクタの引数を同じ継承階層で複数回指定できなくなりました。
引数を持つコンストラクタを、間違った引数数で呼び出すことはできなくなりました。 引数を与えずに継承関係だけを指定したい場合は、括弧を一切付けないでください。
関数
callcode関数は、現在では使用できません(delegatecallに変更)。 ただし、インラインアセンブリで使用することは可能です。
suicideは(selfdestructを優先して)不許可になりました。
sha3は(keccak256を優先して)不許可になりました。
throwは現在、(revert、require、assertに代わって)不許可となっています。
変換
10進数のリテラルから
bytesXX型への明示的、暗黙的な変換ができなくなりました。
16進数のリテラルから異なるサイズの
bytesXX型への明示的および暗黙的な変換ができなくなりました。
リテラルと接尾辞
単位表記の
yearsは、うるう年の複雑さと混乱のため、現在は認められていません。
数字を含まない末尾のドットは使用できません。
16進数と単位表記(例:
0x1e wei)の組み合わせができなくなりました。
16進数の接頭辞
0Xは使用できず、0xのみ使用可能です。
変数
空の構造体を宣言することは、わかりやすくするために禁止されました。
varキーワードを使用しないようにしたことで、明示性が確保されました。
コンポーネントの数が異なるタプル間の割り当てができなくなりました。
コンパイル時の定数ではない定数の値は許されません。
値の数が不一致の複数変数の宣言ができなくなりました。
初期化されていないストレージ変数が禁止されるようになりました。
空のタプル構成要素が許されなくなりました。
変数や構造体の周期的な依存関係の検出は、再帰的に256に制限されます。
長さがゼロの固定サイズの配列が禁止されるようになりました。
シンタックス
constantを関数のステートミュータビリティのモディファイアとして使用できなくなりました。
ブール式では、算術演算は使えません。
単項の
+演算子が使えなくなりました。
リテラルは、事前に明示的な型に変換することなく、
abi.encodePackedで使用できなくなりました。
1つ以上の戻り値を持つ関数の空の戻り文は認められなくなりました。
つまり、ジャンプラベルやジャンプ、機能しない命令はもう使用できません。 代わりに新しい
while、switch、if構文を使ってください。
実装のない関数では、モディファイアが使えなくなりました。
名前付きの戻り値を持つ関数型が禁止されるようになりました。
ブロックではないif/while/forボディ内の単一の文の変数宣言が禁止されました。
新しいキーワードです。
calldataとconstructorです。
新しい予約キーワードです:
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 と宣言されていたにもかかわらず、 anotherOldFunction を view と宣言していないことに注意してください。
これは、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));
}
}