OpenZeppelinのAddress.isContractが削除されていた話
はじめに
本記事はOpenZeppelin
のライブラリを使用してスマートコントラクトを開発している方向けの記事です。
バージョン5.0.0でAddress.isContract
が削除されていたので、なぜ削除されたかを考えてみました。
Address.isContract
が削除された背景を考える
Address.isContract
は、OpenZeppelin
のAddress
ライブラリに含まれる関数で、指定されたアドレスがコントラクトの場合、true
を返す機能を持っていました。
ChangeLogには以下のように記載されています。
- `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.isContract
はfalse
を返します。
状況によってはtrue
を返してほしいと思うかもしれません。
ケース④ 同一トランザクション内でコントラクトが削除されていた場合
同一トランザクション内で、selfdestruct
関数でコントラクトを削除後、その削除済みコントラクトアドレスをAddress.isContract
に渡すとtrue
が返ってきます。
この状況では、false
が返ってきてほしいと思うこともあるでしょう。
特に問題となりそうな誤解
また、Address.isContract
がfalse
を返す場合、そのアドレスはEOA
であると誤解する可能性があります。
-
Address.isContract
がtrue
ということはコントラクトである - アドレスは
EOA
かコントラクト
のどちらかである -
Address.isContract
がfalse
というのはコントラクトでない
ということであり、つまりEOA
である
このロジックは一見正しいように見えます。しかし、上記のケースを考慮するとAddress.isContract
がfalse
を返したとしても、EOA
であるとは限りません。
まとめ
上記ケースのように、true
であってほしいけどfalse
が返ってくる(またはその逆)、という状況が発生するため、Address.isContract
は扱いが難しい関数だと考えられます。
また、特定の状況における判定をするのも難しいため、Address.isContract
を削除する判断に至ったのだと思います。
補足
Address.isContract
を使っていた場合の対応
Address.isContract
を使っていた場合、OpenZeppelin
のライブラリを更新するとコンパイルエラーが発生します。
代替の関数が実装されていないか確認しましたが、特に名前を変えて実装されれている様子も無さそうです。
過去のソースコード(Address.sol)を流用して自前でライブラリを作成する方法が、影響が少ないので良いと思います。
Discussion