TruffleフレームワークとOpenZeppelinライブラリでERC20準拠の独自トークンを実装してみる

[日立グループ OSS Advent Calendar 2021](https://qiita.com/advent-calendar/2021/hitachi-oss) ２０日目の記事です。

(株)日立製作所 研究開発グループ サービスコンピューティング研究部の渡邊です。

本記事は、TruffleとOpenZeppelinを使ったERC20準拠の独自トークンを作成する流れを紹介します。

Ethereumは自律分散型アプリケーションDAppsのプラットフォームです。EthreumでDAppsを開発することの特徴のひとつに独自トークンを作成し、アプリ内通貨として使用できるということがあります。今回は、ERC20に準拠したトークンの実装方法を紹介します。


## はじめに
### ERC20トークンとは
トークン(Token)とは、ブロックチェーン上の資産やユーティリティの総称です。そして、ERC20トークンとは、Ethereumでトークンを発行するための標準規格であるERC20(Ethereum Request for Comments: Token Standard #20)を用いて開発されたトークンの総称です。プロジェクト独自のトークンを発行することができ、ICO(Initial Coin Offering)等でプロジェクト立ち上げ時の資金調達方法として活用されたり、投票やチケットなどのユーティリティ（機能）をもたせたり、有価証券としての役割を担うトークンを作成することができます。ERC20トークンは、いわゆるFungible Tokenで量の概念があります。一方で、ERC721(Non Fungible Token: NFT)は一種類のトークンにつき１個のみ発行されるトークンです。

具体的には、以下の6つのfunctionと2つのeventを実装すればERC20トークンになります。

```js
contract ERC20 {
    function totalSupply() constant returns (uint totalSupply);
    function balanceOf(address _owner) constant returns (uint balance);
    function transfer(address _to, uint _value) returns (bool success);
    function transferFrom(address _from, address _to, uint _value) returns (bool success);
    function approve(address _spender, uint _value) returns (bool success);
    function allowance(address _owner, address _spender) constant returns (uint remaining);
    event Transfer(address indexed _from, address indexed _to, uint _value);
    event Approval(address indexed _owner, address indexed _spender, uint _value);
}
```

## ERC20トークンを作成する流れ
ERC20トークンを作成します。今回はライブラリを活用してERC20トークンのコントラクトを作成し、
それをローカルマシン上の仮想的なプライベートチェーンにデプロイするまでを行います。

### 全体の流れ

1. 開発環境(Truffleフレームワーク)の導入
1. ネットワークの準備
    1. Ganacheの導入
    1. Ganacheの起動
    1. TruffleとGanacheの接続確認
1. 独自トークン スマートコントラクトの実装
    1. OpenZeppelinライブラリの導入
    1. コントラクト(Solidityコード)の作成
    1. コントラクト(Solidityコード)のコンパイル
    1. マイグレーションファイルのデプロイ
    1. コントラクト(Solidityコード)のデプロイ
1. 独自トークン スマートコントラクトの動作確認


### 前提環境
- MacOS Monterey 12.0.1
- Solidity v0.5.0 (solc-js)
- node v12.14.0
- Web3.js v1.3.5
- npm 7.12.1

### 開発環境(Truffleフレームワーク)のインストール
Truffleはスマートコントラクトのコンパイルやデプロイを自動化するためのコマンドラインツール群です。

まず、下記コマンドでtruffleをインストールします。

```
$ npm install -g truffle
+ truffle@5.3.6
Added 1775 packages from 1208 contributors in 176.648s
```

次に、新しくプロジェクトフォルダを作成して、truffle initを実行します。

```bash
$ mkdir ~/my_erc20_token
$ cd ~/my_erc20_token
$ truffle init
✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
✔ Setting up box

Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test

$ teee .
.
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
└── truffle-config.js

3 directories, 3 files

```

- contracts : この中にSolidityで記述されたプログラムを配置します。
- migrations : このディレクトリ内のファイルはマイグレーションファイルと呼ばれ、作成したコントラクトをネットワークにデプロイするために使います。
- test : コントラクトが正しく動作するかを確認するためのテストコードが配置されます。
- truffle-config.js : Truffleの設定ファイルです。詳細は以下で説明します。

### ネットワークの準備
#### Ganacheの導入
Ganacheは簡単に作成できるEthereumのプライベートネットワークです。
起動してすぐにEthを持つアカウントが作成されたり、チェーンをリセットできたりと、DAppsの開発・テストに便利です。デスクトップアプリとしても、コマンドラインツールとしても使えます。

公式サイトでインストーラをダウンロードしてインストールします。

#### Truffle設定
Ganacheで作成したプライベートネットワークと連携するために、configファイルを修正して、ネットワークを設定します。
Ganacheはデフォルトでlocalhost:7545でRPC通信するため以下のように設定します。

```js
module.exports = {
    networks: {
      development: {
         host: "127.0.0.1",     // Localhost (default: none)
         port: 7545,            // Standard Ethereum port (default: none)
         network_id: "*",       // Any network (default: none)
      }
    }
}
```

#### Ganache起動
インストールしたGanacheを起動すると以下の画面が表示されるのでNEW WORKSPACEを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541265/d523f7a2-fa17-b75d-a863-811452cae752.png)

ワークスペースの名前設定と「リンク」を行うための設定画面に移ります。

ADD PRPJECTをクリックして、先ほど作成したプロジェクトディレクトリのtruffle-config.jsを追加することで、
既存のTruffleプロジェクトとGanacheのワークスペースをリンクさせることができます。

下の画像のように、先ほど作ったTruffleプロフェクトをリンクさせました。リンクは後からでも行えますし、アンリンクすることもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541265/847568c8-007b-f896-b990-de15efd9cb1b.png)

成功すると、100ETHをもったアカウントが自動で10個生成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541265/238b94a8-6aae-e014-3256-d657033931e9.png)

これでEthereumプライベートネットワークをローカルに作成することができました。

#### 確認
TruffleからGanacheのプライベートネットワークに接続できるか確認してみます。
プロジェクトディレクトリでtruffle consoleと打つとコンソールへ接続できます。

```
$ truffle console
truffle(development)> 
```

エラーが表示されなければ成功です。




### 独自トークンコントラクトを実装

#### OpenZeppelinを導入

OpenZeppelinはEthereumのスマートコントラクト開発を補助するライブラリです。ERC20トークンを実装するには、OpenZeppelinライブラリに含まれるERC20トークン実装を利用します。

```bash
$ npm init –f
$ npm install @openzeppelin –save
added 1 package, and audited 2 packages in 1s
```
これにより、OpenZeppelinのコードを独自のトークンコントラクトの中から使えるようになります。

#### コントラクトコードの作成

```bash
$ truffle create contract MyToken
```

これでcontractsの中にMyToken.solが作成されます。これをひな形にコードを修正していきます。

```solidity
pragma solidity ^0.8.0;
    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    contract MyToken is ERC20 {
    constructor () ERC20("MyToken", "MTK") {
    _mint( msg.sender, 10000 * 10 ** decimals());
}
```
上記コードでは、トークン名称がMyToken、トークンのシンボルがMTKN、小数点桁数が18、発行したトークンすべてをmgs.senderのアドレス（口座）に入れるようにしています。

#### コントラクトコードのコンパイル
```
$ truffle compile

Compiling your contracts...
===========================
> Compiling ./contracts/HelloWorld.sol
> Compiling ./contracts/Migrations.sol
> Artifacts written to ~/hello_world/build/contracts
> Compiled successfully using:
   - solc: 0.5.12+commit.7709ece9.Emscripten.clang

$ tree .
.
├── build
│   └── contracts
│       ├── Context.json
│       ├── ERC20.json
│       ├── IERC20.json
│       ├── IERC20Metadata.json
│       ├── Migrations.json
│       └── MyToken.json
├── contracts
│   ├── Migrations.sol
│   └── MyToken.sol
├── migrations
├── package-lock.json
├── package.json
├── test
└── truffle-config.js

115 directories, 544 files
```

コンパイルが成功すると、成果物はbuild/contractsフォルダ配下に配置されます。

#### マイグレーションファイルの作成
コントラクトをネットワーク似デプロイするためのマイグレーションファイルを作成します。

デプロイの順番や状態を管理するために、マイグレーションファイル名を[数字]_[名称].jsという形式にする必要があります。


```bash
$ truffle create migration MyToken
```

```js
const MyToken = artifacts.require('./MyToken.sol')
module.exports = (deployer) => {
    const initialSupply = 50000e18
    deployer.deploy(MyToken, initialSupply)
}

```
トークン発行量が50,000MTKNになるように、MyTokenのコンストラクタへinitialSupplyの値を渡しておきます。


#### コントラクトコードのデプロイ
migrateコマンドでmigrateディレクトリ配下のスクリプトを実行していきます。

```bash
$ truffle migrate
```

```
Starting migrations...
======================
> Network name:    'development'
> Network id:      5777
> Block gas limit: 0x6691b7


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0xe6cc1b0d4f3e8a79c64be1e9a0f0daf9bae7908bfc76ee075b10c5f0cbc0364d
   > Blocks: 0            Seconds: 0
   > contract address:    0x679357F71B9B097B6546188DAb4E47aDA516DB63
   > block number:        1
   > block timestamp:     1575113348
   > account:             0x2cC796eCE69f4Bf41f4Cc6D3733FE362D921F9a5
   > balance:             99.99472518
   > gas used:            263741
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00527482 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00527482 ETH


2_deploy_my_tokens.js
=========================

   Deploying 'MyToken'
   ----------------------
   > transaction hash:    0x6fcb1f9fd8bd7761c676448d64c571da7d7945fe25ec599dba6e2ce8e17edbe9
   > Blocks: 0            Seconds: 0
   > contract address:    0x8300Be5250A6323A7A83d220157e4a6c2A980659
   > block number:        3
   > block timestamp:     1575113348
   > account:             0x2cC796eCE69f4Bf41f4Cc6D3733FE362D921F9a5
   > balance:             99.99000202
   > gas used:            194135
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.0038827 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:           0.0038827 ETH


Summary
=======
> Total deployments:   2
> Final cost:          0.00915752 ETH
```

前回デプロイが成功だった場合、それ移行の新しいマイグレーションだけが実行されます。
デプロイ済みのコントラクトコードを修正した場合は以下のコマンドを実行することで新しいコントラクトコードでデプロイし直す必要があります。

```
$ truffle migreate --reset
```

#### 確認

２回migrateを行ったのでGanacheでネットワークのログを確認してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541265/9a5d671b-a44e-9cdc-fd32-6c67da7eb60d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541265/a528e0ce-e6b5-01c1-4c0a-308a5ac39317.png)


先頭のアカウントのETH残高が減っています。トランザクションログを見てみると8個のログが記録されていました。

### MyTokenの動作確認

MyTokenコントラクトの動作確認をしてみます。

```js
truffle(develop)> myToken = MyToken.at(MyToken.address)
```

以下を実行すると、トークン名称、トークン発行総量が確認できます。

```js
truffle(develop)> myToken.name()
'MyToken'
truffle(develop)> myToken.totalSupply()
BigNumber { s: 1, e: 22, c: [ 500000000 ] }
```

次に、アドレスの一覧を表示してみます。

```js
truffle(develop)> web3.eth.accounts
[ '0x0...', ... ]
```

続いて、アドレスの口座残高を確認してみます。デプロイ時に指定したとおり、一番目のアドレス(account[0])に発行したすべてのトークンが格納されており、その他のアドレスの口座段高はゼロであることが確認できます。

```js
truffle(develop)> myToken.balanceOf(web3.eth.accounts[0])
BigNumber { s: 1, e: 22, c: [ 500000000 ] }

truffle(develop)> myToken.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 0, c: [ 0 ] }
```

最後に、account[0]からaccount[1]に、1,000MTKN移動してみます。

```js
truffle(develop)> myToken.transfer(web3.eth.accounts[1], 1000e18)
...

truffle(develop)> myToken.balanceOf(web3.eth.accounts[0])
BigNumber { s: 1, e: 22, c: [ 490000000 ] }

truffle(develop)> myToken.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 21, c: [ 10000000 ] }
```

## まとめ
TruffleとOpenZeppelinでERC20準拠の独自トークンを実装する流れの紹介は以上です。OpenZeppelinを使うことで独自トークンを簡単に実装することができました。また、TruffleやGanacheを使うことで、デプロイや動作確認、テストを簡単にできるようになりました。皆様も一度試してみてはいかがでしょうか。

## 参考文献
- [Truffle公式のチュートリアル](https://www.trufflesuite.com/docs/truffle/quickstart)
- [Ganache公式のチュートリアル](https://www.trufflesuite.com/docs/ganache/quickstart)
- [TruffleとGanacheを使ったÐapps開発の環境構築 ＋ チュートリアル](https://qiita.com/romorimori/items/155bb1f5e4cced629ce8)
- [Truffle で始める Ethereum 入門 - ERC20 トークンを作ってみよう](https://qiita.com/amachino/items/8cf609f6345959ffc450)