🐡

OpenZeppelinのAddress.isContractが削除されていた話

2024/08/19に公開

はじめに

本記事はOpenZeppelinのライブラリを使用してスマートコントラクトを開発している方向けの記事です。

バージョン5.0.0でAddress.isContractが削除されていたので、なぜ削除されたかを考えてみました。

Address.isContractが削除された背景を考える

Address.isContractは、OpenZeppelinAddressライブラリに含まれる関数で、指定されたアドレスがコントラクトの場合、trueを返す機能を持っていました。

ChangeLogには以下のように記載されています。

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/dbb6104ce834628e473d2173bbc9d47f81a9eec3/CHANGELOG.md?plain=1#L34

- `Address.isContract` (because of its ambiguous nature and potential for misuse)

because of its ambiguous nature and potential for misuse
[DeepL訳] なぜなら、その曖昧な性質と誤用の可能性があるからである。

これについて掘り下げます。

元々、Address.isContract指定されたアドレスのバイトコードが存在する場合にtrueを返す処理を行う関数でした。

「バイトコードが存在するならコントラクトという判定で問題ない」と思われるかもしれませんが、意図しない判定となるケースがあります。

ケース① デプロイ中のコントラクトアドレス

コントラクトをデプロイする時、そのコントラクトアドレスをAddress.isContractに渡すと、falseが返ってきます。

コンストラクタ内で攻撃対象のコントラクトメソッドを呼び出すことで、コントラクトからの呼び出しにも関わらずコントラクトアドレスでないと誤認させることができます。

ケース② 将来的にそのアドレスにコントラクトがデプロイされる場合

コントラクトアドレスは、デプロイ前に計算が可能です。
例えば以下のようなコントラクトがあった場合、デプロイ前にアドレスを登録しておくことができます。

contract BadContract {
    mapping(address eoaAddresses => bool isEOA) isEOA;

    function badRegisterEOA(address eoaAddress) public {
        // ここでコントラクトの場合はrevertしているつもりだが、
        // コントラクトが未デプロイの場合`false`が返ってくる
        if(Address.isContract(eoaAddress)) {
            revert("BadContract: EOA only");
        }
        isEOA[eoaAddress] = true; // ← コントラクトアドレスも登録されてしまう可能性がある
    }
}

ケース③ コントラクトを削除した場合

一度コントラクトをデプロイした後、selfdestruct関数でそのコントラクトを削除した場合、Address.isContractfalseを返します。
状況によってはtrueを返してほしいと思うかもしれません。

ケース④ 同一トランザクション内でコントラクトが削除されていた場合

同一トランザクション内で、selfdestruct関数でコントラクトを削除後、その削除済みコントラクトアドレスをAddress.isContractに渡すとtrueが返ってきます。
この状況では、falseが返ってきてほしいと思うこともあるでしょう。

特に問題となりそうな誤解

また、Address.isContractfalseを返す場合、そのアドレスはEOAであると誤解する可能性があります。

  1. Address.isContracttrueということはコントラクトである
  2. アドレスはEOAコントラクトのどちらかである
  3. Address.isContractfalseというのはコントラクトでないということであり、つまりEOAである

このロジックは一見正しいように見えます。しかし、上記のケースを考慮するとAddress.isContractfalseを返したとしても、EOAであるとは限りません。

まとめ

上記ケースのように、trueであってほしいけどfalseが返ってくる(またはその逆)、という状況が発生するため、Address.isContractは扱いが難しい関数だと考えられます。

また、特定の状況における判定をするのも難しいため、Address.isContractを削除する判断に至ったのだと思います。

補足

Address.isContractを使っていた場合の対応

Address.isContractを使っていた場合、OpenZeppelinのライブラリを更新するとコンパイルエラーが発生します。
代替の関数が実装されていないか確認しましたが、特に名前を変えて実装されれている様子も無さそうです。

過去のソースコード(Address.sol)を流用して自前でライブラリを作成する方法が、影響が少ないので良いと思います。

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/a4596cab053d46e0bf2957e2ed490cb3921539ee/contracts/utils/Address.sol#L40-L46

Discussion