🐕

Hardhatで作ったスマートコントラクトをOpenZeppelinでリファクタリングしてみた

に公開

はじめに

OpenZeppelinとは、スマートコントラクトでよく利用する関数や処理の安全で信頼できるテンプレ集のようなものです。
特にイーサリアム系の開発者にとっては、トークン、アクセス制御、ガバナンスなどの機能を実装する際に、OpenZeppelinのライブラリを使うのが基本となっています。

https://zenn.dev/barabara/articles/61611e217fbd07
以前こちらで簡単なDAppを作成しましたが、その時は素のSolidityで書いていたので、今回はOpenZeppelinを使ってスマートコントラクトをリファクタリングしてみます。

元のコード

元のコードはこちらです。

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

contract Token {
    // トークンの名前
    string public name = "My Hardhat Token";
    // トークンのシンボル
    string public symbol = "MHT";

    // トークンの固定供給量(uint256型の整数で保存)
    uint256 public totalSupply = 1000000;

    // イーサリアムアカウントを保存するためのアドレス型変数
    address public owner;

    // 各アカウントの残高を保持するマッピング
    mapping(address => uint256) balances;

		// イベントを定義 from(送信者アドレス) to(受信者アドレス) amount(送金額)
    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    // デプロイ時に一度だけ実行
    constructor() {
        // totalSupplyをコントラクトのデプロイアカウントに割り当てます。      
        balances[msg.sender] = totalSupply;
        owner = msg.sender;
    }

    // トークン送信用の関数
    function transfer(address to, uint256 amount) external {
        // 送信者が十分なトークンを持っているか確認します。
        // NOTE: unicodeを入れないとInvalid character in string literalエラーが出る
        require(balances[msg.sender] >= amount,unicode"トークンが不足しています");

        // トークンの送信
        balances[msg.sender] -= amount;
        balances[to] += amount;
        // イベントを発行 送金者、受取人、送金額がログに記録される
        emit Transfer(msg.sender, to, amount);
    }

    // 指定したアカウントのトークン残高を取得する関数
    function balanceOf(address account) external view returns (uint256) {
        return balances[account];
    }
}

OpenZeppelinを使った新実装

こちらがOpenZeppelinを使ってリファクタリングしたコードになります。

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/// @title Hardhat Token(MHT)
/// @notice このコントラクトはデモンストレーション用の簡単なERC20トークンを実装します
/// @dev OpenZeppelinのERC20実装を継承しています
contract Token is ERC20 {
    /// @notice コントラクトオーナーのアドレス
    /// @dev コントラクトのデプロイ時に設定されます
    address public owner;

    /// @notice トークン名を"My Hardhat Token"、シンボルを"MHT"として初期化します
    /// @dev コントラクトのデプロイヤーに初期供給量をミントします
    constructor() ERC20("My Hardhat Token", "MHT") {
        owner = msg.sender;
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }
}

OpenZeppelinを利用すると、使用前よりかなり簡潔にコードが書けるようになりますね。その理由を以下に説明します。

再利用可能な標準ライブラリが提供されているから

OpenZeppelinは、ERC20などのトークン標準の完全で安全な実装を提供しています。これにより、開発者は標準規格の詳細を理解したり、一から実装したりする必要がなくなります。

継承と抽象化により、冗長なコードが不要になるから

OpenZeppelinのERC20コントラクトは内部であらゆる機能を抽象化して持っているため、必要な設定だけ書けばOKになります。

例えば、constructor()でトークン名・シンボルを渡すだけで、他の機能はすべて親コントラクトに実装されているものが使えます。

constructor() ERC20("My Hardhat Token", "MHT") {
    _mint(msg.sender, 1000000 * 10 ** decimals());
}

この1行だけで、totalSupplyが100万トークンに設定され、msg.senderのアドレスにトークンが割り当てられます。さらに、小数点(decimals)を考慮した単位でミント(新しくトークンを発行)する処理まで、すべて実現されます。
また、ERC20コントラクトを継承することによって、balanceOf(), transfer() などの関数が何も書いてないのに使うことができます。

実際にGitHub上のOpenZeppelinのコードを見てみると、これらの機能がどのように実装されているかがよく分かります。

セキュリティ監査と信頼性が高い

OpenZeppelinは業界標準で、多くのプロジェクトが使用しています。プロのセキュリティ監査を受けており、自分自身でゼロからコードを書くより遥かに安全です。
スマートコントラクトは一度デプロイするとコードの変更ができないという特性上、このような信頼性の高いライブラリを使った開発が主流になっていると考えられます。

余談

https://deepwiki.com/OpenZeppelin/openzeppelin-contracts/tree/master
最近、GitHubリポジトリのURLに含まれるgithub.comの部分をdeepwiki.comに置き換えるだけで、そのリポジトリのコードをAIが自動解析し、詳細な解説ドキュメントを生成してくれるDeepWikiというツールが無料で使えるようになりました。
AI開発ツール「Devin」で知られるCognition AIが開発しています。

READMEだけではコード全体の詳細な把握が難しい場合などに、ChatGPTのように対話形式で質問することも可能です。
実際のコードを基にドキュメントが生成されるため、非常に精度が高いという印象を受けます。

コードの理解が格段に速くなりそうですね。皆さんもぜひ試してみてはいかがでしょうか。

まとめ

今回は、スマートコントラクト開発における強力な味方、OpenZeppelinライブラリを使って既存のトークンコントラクトをリファクタリングしてみました。

OpenZeppelinを利用することで、

  • コードの記述量が大幅に削減される
  • ERC20などの標準規格に準拠した安全なコントラクトを容易に実装できる
  • 実績のある監査済みコードベースにより、セキュリティリスクを低減できる

といった大きなメリットがあることを実感いただけたのではないでしょうか。

スマートコントラクト開発は、一度デプロイすると変更が難しいという特性上、信頼性の高いライブラリの活用が非常に重要です。ぜひ、皆さんのプロジェクトでもOpenZeppelinを導入して、安全で効率的な開発を体験してみてください。

Discussion