Cover photo

Web3勉強記: FoundryでERC20トークンをデプロイする

@hatoneです。 Web3といえばSTEPNで遊んだくらい…だったのですが、Etherscanのログを眺めるところからはじまり、そろそろ自分で実装・テスト・デプロイを一通り理解したいと思い立ったので、学んだことまとめを書いてみます。

フレームワーク| Truffle、Hardhat、Foundry

Truffle、Hardhat、Foundryは、いずれもEthereumスマートコントラクトの開発を支援するフレームワークですが、特徴や強みが異なります。Truffleは長い間業界標準だったようですが、Hardhatがより柔軟で拡張性の高い代替手段として盛り上がってきているらしいです。Foundryは比較的新しいツールで、その速度と効率性から急速に注目を集めています。特に、堅牢なテストとファジング機能が高く評価されています。実際に、私が他のWeb3エンジニアの方から勧めてもらったのはFoundryでした。

Truffle

  • 最も歴史のあるスマートコントラクト開発フレームワークの一つで、広く採用されている

  • Ganacheという組み込みのローカルEthereumネットワークを提供し、テストやデバッグを容易にてる

  • コントラクトのコンパイル、テスト、デプロイのためのコマンドラインツールがある

  • Truffleコンソールを使用して、インタラクティブにコントラクトとやり取りができる

Hardhat

  • Hardhat Network は、フォークやデバッグトレースなどの高度な機能を備えたローカルEthereumネットワーク

  • プラグインアーキテクチャを採用しており、プラグインを通じて機能を拡張

  • Typescript のサポートが組み込まれており、型安全な開発が可能

今回選択したFoundry

  • Rustで書かれた新しいスマートコントラクト開発ツールセット

  • コンパイル、テスト、デプロイのためのコマンドラインツール。そして速い

  • Forgeテストフレームワークは、スマートコントラクトのファジングやプロパティベースのテストを可能

  • Vyper というスマートコントラクト言語のサポート

Foundryは、Solidityスマートコントラクトの開発、テスト、デプロイのための強力なツールセットです。Truffle、Hardhatなどの他のフレームワークと比較して、Foundryは高速なコンパイル、テストの実行、そしてネイティブのFuzzテストをサポートしています。

今回、Foundryを選択した理由は以下の通り:

  • ネイティブのFuzzテストをサポート

  • ドキュメントが割としっかりしてる

  • Rustで書かれており、高速で効率的

  • Solidityとシームレスに統合

開発言語 | SolidityとVyper

SolidityとVyperは、どちらもEthereumスマートコントラクトを開発するための高レベルプログラミング言語ですが、設計思想や目的が異なります。 Solidityは、JavaScriptに似た構文を持ち、オブジェクト指向プログラミングをサポートしています。一方、Vyperは、Pythonに影響を受けた構文を持ち、シンプルで読みやすいコードを書くことに重点を置いています。言語機能としてVyperはモジュラー性を重視するあまり、機能が絞られているようです。ただ、シンプルな機能を実装をしたい場合、Vyperで提供されている機能内で収まりそうなら、そっちを使うという将来がありそうな気がしました。今回はSolidityを使用しましたが、Vyperでのスマートコントラクト開発にも挑戦してみたいです。

ステップ0: Foundryインストール

curl -L https://foundry.paradigm.xyz | bash
foundryup

エラー: libusb-1.0.0.dylib が見つからない

私の環境では、forgeコマンドを実行した際に必要なライブラリ libusb-1.0.0.dylib が見つからないという問題が発生してていました。brew install libusbで入れておきました。

謎のメッセージ:

rustup update stable
info: syncing channel updates for 'stable-x86_64-apple-darwin'

  stable-x86_64-apple-darwin unchanged - rustc 1.76.0 (07dca489a 2024-02-04)

info: self-update is disabled for this build of rustup
info: any updates to rustup will need to be fetched with your system package manager

このメッセージはRustの安定版が最新であること、そしてrustupの自己更新がこのビルドでは無効であるため、システムのパッケージマネージャーを通じてrustupを更新する必要があることを示しています。brew upgrade rustupで上げました。

ステップ1: プロジェクトのセットアップ

まず、新しいFoundryプロジェクトを作成しましょう。ターミナルで以下のコマンドを実行します

forge init MyToken
cd MyToken

次に、OpenZeppelinライブラリをインストール

forge install OpenZeppelin/openzeppelin-contracts

OpenZeppelin Contractsとはなにか

OpenZeppelin Contractsは、セキュリティと再利用性を重視したスマートコントラクトのオープンソースライブラリです。このライブラリは、Solidityで書かれており、以下のような一般的なスマートコントラクトの機能を提供しています。

  • ERC20トークン:fungibleトークン(互換性のあるトークン)を実装するための標準インターフェース

  • ERC721トークン:非fungibleトークン(NFT)を実装するための標準インターフェース

  • アクセス制御:スマートコントラクトの関数へのアクセスを管理するためのロールベースのアクセス制御システム

  • アップグレード可能なコントラクト:スマートコントラクトをアップグレードするためのメカニズム

OpenZeppelin Contractsを使用する主なメリットは、一般的な機能を実装する際に、ゼロから書く代わりにこれらのコントラクトを使用することで、開発時間を短縮できること。これらのコントラクトは、コミュニティによって広くレビューされ、テストされているため、セキュリティの欠陥やバグのリスクが少ない。今回はそんな巨人の肩に乗せてもらいます。

ステップ2: ERC20トークンの作成

srcディレクトリにMyToken.solファイルを作成し、以下のコードを追加します。

pragma solidity ^0.8.0;

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

contract MyToken is ERC20 {
    uint8 private constant _decimals = 18;

    constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
        _mint(msg.sender, initialSupply * 10 ** _decimals);
    }

    function decimals() public view virtual override returns (uint8) {
        return _decimals;
    }
}

OpenZeppelin ContractsのERC20コントラクトを継承することで、独自のERC20トークンを簡単に作成しました。これにより、トークンの基本的な機能(転送、残高の確認など)を安全に実装することができました。コンストラクタでは、トークンの名前、シンボル、初期供給量を設定します。

ステップ3: テストの作成

testディレクトリにMyToken.t.solファイルを作成し、以下のコードを追加します。

pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../src/MyToken.sol";

contract MyTokenTest is Test {
    MyToken public token;
    address public deployer = address(1);

    function setUp() public {
        vm.prank(deployer);
        token = new MyToken(1000);
    }

    function testInitialSupply() public view {
        assertEq(token.totalSupply(), 1000 * 10 ** 18);
        assertEq(token.balanceOf(deployer), 1000 * 10 ** 18);
    }
}

このテストでは、トークンの初期供給量が正しく設定されているかを確認します。

ステップ4: テストの実行

以下のコマンドを実行して、テストを実行します。

forge test

すべてのテストがパスすれば、コントラクトが期待通りに動作していることが確認できます。

ステップ5: デプロイスクリプトの作成

scriptディレクトリにdeploy.s.solファイルを作成し、以下のコードを追加します。

pragma solidity ^0.8.0;

import "../src/MyToken.sol";
import "forge-std/Script.sol";

contract Deploy is Script {
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);
        new MyToken(0);
        vm.stopBroadcast();
    }
}

このスクリプトは、MyTokenコントラクトを0トークンの初期供給量でデプロイします。

ステップ6: 環境変数の設定

プロジェクトのルートディレクトリに.envファイルを作成し、以下の変数を設定します。

RPC_URL=your_rpc_url
PRIVATE_KEY=your_private_key
ETHERSCAN_API_KEY=your_etherscan_api_key

your_rpc_urlyour_private_keyyour_etherscan_api_keyを実際の値に置き換えてください。これらの値は、それぞれAlchemy、MetaMask、Etherscanから取得します。

ステップ6-1: Alchemyアカウント取得

Alchemyは、イーサリアムや他のブロックチェーンネットワークへのアクセスを提供するサービスです。以下の手順でAlchemyアカウントを作成し、RPCエンドポイントを取得します。 アプリを新規作成する際に、Chainをどれにするか聞かれるので、Sepoliaを選びましょう。

「View Key」をクリックすると、HTTP URLとWebSocket URLが表示されます。このうち、HTTP URLがyour_rpc_urlに相当します。

ステップ6-2: SepoliaETH取得

post image

SepoliaETHを自分のウォレットへ送るボタンを押下後、ちょっと怖いようなカッコいいようなダークサイバーな感じの演出があります。アングラ感がちょっとあって、正直ワクワクしました。

ステップ6-3: ETHERSCAN_API_KEYの取得

post image

ステップ7: デプロイの実行

以下のコマンドを実行して、MyTokenコントラクトをSepoliaテストネットワークにデプロイします。

forge script script/deploy.s.sol --rpc-url $RPC_URL --broadcast --verify -vvvv

デプロイが成功すると、Ethescan上で見えるようになります。 私がテストネットワークにデプロイした様子はこちらです。

次のやりたいこと

  • デプロイされたコントラクトとのインタラクションを試す

  • トークンの機能

  • メインネットワークへのデプロイ(Etheriumのメインネットのガス代高いですね……

さいごに

Foundryを使用して、スマートコントラクトの開発はじめの一歩、テスト、デプロイをやってみました。つまづくポイントも少なく、独自のERC20トークンを作成する方法を学びはじめることが出来ました。 新しい知識を自分で学ぶ力が弱くなっている不安があったので、細く長く楽しんでWeb3開発を学んでいこうと思います。