[Solidityのはじめ方] Part18: オーバーフロー/アンダーフロー対策

Solidity

Solidityにおける数値計算の落とし穴とその対策 🔢

スマートコントラクトでは、数値を扱う計算が頻繁に行われます。特に、トークンの移転や残高管理など、お金に関わる処理では計算の正確性が極めて重要です。しかし、コンピュータの数値表現には限界があり、それがオーバーフローアンダーフローといった問題を引き起こす可能性があります。

オーバーフロー (Overflow): データ型が表現できる最大値を超えてしまい、結果が最小値(またはそれに近い値)に戻ってしまう現象。

アンダーフロー (Underflow): データ型が表現できる最小値(特に符号なし整数では0)を下回ってしまい、結果が最大値(またはそれに近い値)になってしまう現象。

これらは、予期せぬ計算結果を生み出し、コントラクトのロジックを破壊したり、セキュリティ上の深刻な脆弱性となったりする可能性があります。😱

Solidity 0.8.0 未満の挙動

Solidity バージョン 0.8.0 未満では、デフォルトでオーバーフロー/アンダーフローのチェックが行われませんでした。これは、開発者が意図しない限り、計算結果がラップアラウンド(循環)してしまうことを意味します。

例を見てみましょう:

// SPDX-License-Identifier: MIT
// 注意: このコードは Solidity 0.8.0 未満を想定しています。
// 現在のコンパイラではデフォルトでエラーになります。
pragma solidity ^0.7.0;

contract VulnerableCalculator {
    uint8 public maxUint8 = type(uint8).max; // 255
    uint8 public minUint8 = type(uint8).min; // 0

    // オーバーフローの例
    function addOverflow() public view returns (uint8) {
        // 255 + 1 は 0 になってしまう
        return maxUint8 + 1;
    }

    // アンダーフローの例
    function subUnderflow() public view returns (uint8) {
        // 0 - 1 は 255 になってしまう
        return minUint8 - 1;
    }
}

このコードを Solidity 0.7.x でコンパイルして実行すると、addOverflow0 を、subUnderflow255 を返します。これが意図しない動作であり、悪用される可能性がありました。

この問題を解決するため、0.8.0 未満では OpenZeppelin が提供する SafeMath ライブラリを使用することが一般的でした。

SafeMath ライブラリ

SafeMath は、算術演算を行う際に関数(add, sub, mul, div など)を提供し、これらの関数内部でオーバーフロー/アンダーフローをチェックして、問題が発生した場合はトランザクションをリバート(中断)させるライブラリです。0.8.0 未満のプロジェクトでは広く使われていました。
// Solidity 0.7.x で SafeMath を使う例
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

import "@openzeppelin/contracts/utils/math/SafeMath.sol"; // SafeMathをインポート

contract SafeCalculator {
    using SafeMath for uint256; // uint256型にSafeMathを適用

    uint256 public value = 100;

    function safeAdd(uint256 _add) public {
        value = value.add(_add); // SafeMathのadd関数を使用
    }

    function safeSub(uint256 _sub) public {
        value = value.sub(_sub); // SafeMathのsub関数を使用
    }
}

※ 現在 (Solidity 0.8.0 以降) では、後述の通り言語レベルで対策が組み込まれたため、SafeMath の必要性は大幅に低下しました。

参考: OpenZeppelin SafeMath (旧バージョン用)

Solidity 0.8.0 以降の挙動 ✨

開発者コミュニティからのフィードバックとセキュリティの観点から、Solidity バージョン 0.8.0 以降では、デフォルトで算術演算に対するオーバーフローおよびアンダーフローのチェックが有効になりました!🎉 これは大きな改善点です。

つまり、特別なライブラリを使わなくても、通常の +, -, * などの演算子でオーバーフローやアンダーフローが発生した場合、トランザクションは自動的にリバートされます。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; // 0.8.0 以降を指定

contract SecureCalculator {
    uint8 public value = 255;

    // この関数を実行すると、255 + 1 でオーバーフローが発生し、
    // トランザクションが revert される
    function addOne() public {
        value = value + 1;
    }

    uint8 public anotherValue = 0;

    // この関数を実行すると、0 - 1 でアンダーフローが発生し、
    // トランザクションが revert される
    function subtractOne() public {
        anotherValue = anotherValue - 1;
    }
}

これにより、多くの一般的な算術エラーが自動的に防止されるようになり、コントラクトの安全性が向上しました。

意図的にチェックを無効にする: `unchecked` ブロック

Solidity 0.8.0 以降ではデフォルトでチェックが有効になりましたが、場合によっては(例えば、ループカウンターなどでオーバーフローしないことが確実に分かっている場合や、ガス代を極限まで最適化したい場合など)、このチェックを意図的に無効にしたい場面も存在するかもしれません。

そのために用意されているのが unchecked ブロックです。このブロック内で実行される算術演算は、オーバーフロー/アンダーフローのチェックが行われず、0.8.0 未満の挙動(ラップアラウンド)を示します。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract UncheckedExample {
    uint8 public counter = 255;

    // unchecked ブロック内で演算を行う
    function incrementUnchecked() public {
        unchecked {
            // このブロック内ではチェックされないため、
            // counter は 255 + 1 = 0 になる
            counter = counter + 1;
        }
    }

    function incrementChecked() public {
        // ブロック外では通常通りチェックされるため、
        // counter が 255 の場合に実行すると revert される
        counter = counter + 1;
    }
}
⚠️ 注意: unchecked ブロックの使用は、その挙動を完全に理解し、オーバーフロー/アンダーフローが発生しても問題ない、あるいは絶対に発生しないと確信できる場合に限定してください。不用意な使用は深刻な脆弱性を生む可能性があります。

まとめとベストプラクティス

  • Solidity 0.8.0 以降を使用する: これが最も簡単かつ効果的な対策です。デフォルトで算術チェックが有効になります。
  • unchecked の使用は慎重に: ガス最適化のために unchecked を使用する場合は、その範囲を最小限にし、オーバーフロー/アンダーフローが発生しないことを論理的に確認してください。コメントで使用理由を明記するのも良い習慣です。
  • 古いコード (0.8.0 未満) を扱う場合: SafeMath のようなライブラリが使用されているか、または同等のチェックが手動で実装されているかを確認してください。もし対策されていない場合は、アップグレードまたは修正が必要です。
  • テストを徹底する: 境界値(最大値、最小値、ゼロ)付近での計算を含むテストケースを作成し、意図したとおりに動作すること、予期せぬオーバーフロー/アンダーフローが発生しないことを確認しましょう。

オーバーフロー/アンダーフローは、スマートコントラクト開発において注意すべき基本的なセキュリティ問題の一つです。Solidity 0.8.0 以降のバージョンを利用することで、多くのリスクは軽減されますが、unchecked ブロックの存在や古いコードとの互換性を考えると、この問題に対する理解は依然として重要です。安全なスマートコントラクト開発のため、常に意識しておきましょう!🛡️

コメント

タイトルとURLをコピーしました