🃏

openzeppelinを4系から5系にupdateする

2024/08/15に公開

概要

openzeppelin4系を5系にupdateしました。

かなりの破壊的変更があって更新に苦戦したので、今後updateを控えている人のためにメモを残します。

https://blog.openzeppelin.com/introducing-openzeppelin-contracts-5.0

環境

  • OpenZeppelin 4.9.3 → 5.0.2
  • solidity 8.2.0
  • npm 10.7.0
  • hardhat 2.17.2

作業フロー

  • ライブラリupdate
  • テストを実行
  • エラーを修正
  • 全てのテストが通るまで修正を続ける

ライブラリupdate

メジャーアップデートなので、npm updateコマンドをそのまま入力してもうまくいかないので

npm uninstall @openzeppelin/contracts
npm uninstall @openzeppelin/contracts-upgradeable

で既存ライブラリを削除後に

npm update @openzeppelin/contracts@5.0.2
npm update @openzeppelin/contracts-upgradeable@5.0.2

を実行します。

実行するとpackage.jsonが書き換わります。

package.json
   "dependencies": {
-    "@openzeppelin/contracts": "^4.9.3",
-    "@openzeppelin/contracts-upgradeable": "^4.9.3",
+    "@openzeppelin/contracts": "^5.0.2",
+    "@openzeppelin/contracts-upgradeable": "^5.0.2"
   }

テストの実行とエラー修正

テストを流すと当然失敗します。

compileすらまともにできない状態になりました。

私のコントラクトコードでは、以下の修正が必要になったので紹介します。

1. IERC721Upgradeableが削除されたのでIERC721に置き換える

v5では、IERC721Upgradeable.solのコードがなくなりました。

IERC721と中身が同じだっので削除されたそうです。

なので、IERC721で置き換えます。

SoulBound.sol
+import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
 import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
-import {IERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";

     // 譲渡不可能にする
-    function transferFrom(address from, address to, uint256 tokenId) public override(ERC721Upgradeable, IERC721Upgradeable) {
+    function transferFrom(address from, address to, uint256 tokenId) public override(ERC721Upgradeable, IERC721) {
       require(from == address(0), "Err: token is SOUL BOUND");
       super.transferFrom(from, to, tokenId);
     }

overrideの箇所も変更す必要があります。

2. tokenURI関数でERC721NonexistentTokenがrevertされる

tokenURIでtokenIDがない場合、NonexistentTokenがrevertされるように修正されました。

これはテストコードの修正が必要になるでしょう。

以下のように修正されます。

TestSoulBound.js
     it("should revert when tokenID is not exists", async function () {
-      await expect(NFT.tokenURI(0)).to.be.revertedWith("ERC721: invalid token ID");
+      await expect(NFT.tokenURI(0)).to.be.revertedWith("ERC721NonexistentToken");
     });

3. mint

_mint関数では、zero addressに送信するとERC721InvalidReceiverがrevertされるようになりました。

https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/6e85c7ea069a861906b30884d16193bb93753bd4/contracts/token/ERC721/ERC721Upgradeable.sol#L314C20-L314C41

こちらもテストコードでの修正が必要になるでしょう。

4. transferFrom

ownerでも所持者じゃないaddressからのtransform関数の実行は、ERC721InsufficientApprovalがrevertされるようになりました。

https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/6e85c7ea069a861906b30884d16193bb93753bd4/contracts/token/ERC721/ERC721Upgradeable.sol#L235

こちらもテストコードでの修正が必要になるでしょう。

5. ERC20Votes

ERC20Votesはv4とは別物になっているので、ほぼ書き直しになるでしょう。

ERC20Votesは、ERC20とVotesを継承するabstractクラスになりました。

v4では

V4Sample.sol
contract ERC20VotesToken is ERC20, ERC20Votes {
    constructor() ERC20("ERC20VotesToken", "EVT") {}
}

にように実装していたのが

V5Sample.sol
 import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
 import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
 import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
+import "@openzeppelin/contracts/access/Ownable2Step.sol";

contract ERC20VotesToken is ERC20Votes, Ownable2Step {
    constructor(address owner) ERC20("ERC20VotesToken", "EVT") EIP712("ERC20VotesToken", "1") Ownable(owner) {}
}

のようになりました。

ここで注意して欲しいのが

EIP712.solをconstructorに追加することです。

EIP712.solをconstructorに追加しないとエラーになってしまいます。

また、_mintや_burnもoverrideできないので、mint関数とburn関数を作成して、ERC20の_mintを呼び出す形にコードを修正する必要があります。

V5Sample.sol
     function mint(address to, uint256 amount) public {
         _mint(to, amount);
      }

 
     function burn(address account, uint256 amount) public {
         super._burn(account, amount);
     }

     //function _mint(address to, uint256 amount) internal override(ERC20) {
     //    super._mint(to, amount);
     //}
 
     //function _burn(address account, uint256 amount) internal override(ERC20) {
     //   super._burn(account, amount);
     //}

6. Governor

GovernorCountingSimpleを使っている場合、特に変更がありませんでした。

まとめ

OpenZeppelinのv5系は、今の時代のコントラクトの用途に合わせた変更がされていると思いました。

ただ、それなりの知識がないと、コードを修正するのは厳しいようにも思います。

また、今の時代にnpmでupdateしていくのは色々と辛いと思いました。

かといって他のツールもイマイチなので、しばらくは試行錯誤の時代が続くでしょう。

アップデートを迷っている人は、5.0系のうちにupdateしておいた方が良さそうです。

Discussion