【Solidity】HardhatでDappを作ってみる ~ チュートリアル編（前半）~

## Hardhatとは
いい感じにSolidityでのDapp開発ができるやつ（語彙力皆無）

## 事前準備
- Node.js使えるようにしておく
- VSCode使ってる人はの[Extension](https://marketplace.visualstudio.com/items?itemName=NomicFoundation.hardhat-solidity)をインストールしておくと便利っぽいです

https://marketplace.visualstudio.com/items?itemName=NomicFoundation.hardhat-solidity

## 環境
- OS: MacOS Monterey 12.0.1
- Node.js: 18.5.0
（チュートりアルにインストールするversion書いてあるのでそれに従ってください）

## チュートリアルをやってみる
これをやっていきます
https://hardhat.org/tutorial/creating-a-new-hardhat-project

### プロジェクトの設定

作業用ディレクトリを作って移動
```shell
mkdir hardhat-tutorial
cd hardhat-tutorial
```

npm init すると色々聞かれるけどチュートリアルなので全部EnterでOK
最後に Is this OK? と聞かれるのでyes
```shell
npm init
...
Is this OK? (yes) 
```

そしたらHardhatをインストールします。ちょっと時間かかるかも。
```shell
npm install --save-dev hardhat
```

npx hardhat と打つといくつか選択肢が出てきます。
`Create a TypeScript project`を選択したい気持ちを抑えて指示通りに `Create an empty hardhat.config.js`を選択します。
```shell
npx hardhat

👷 Welcome to Hardhat v2.10.0 👷‍

? What do you want to do? … 
  Create a JavaScript project
  Create a TypeScript project
❯ Create an empty hardhat.config.js
  Quit
```

スマートコンラクトの開発に必要なもの全てが揃うと説明されているプラグインをインストールします。
WARNがいっぱい出てきますが無視します。
```shell
npm install --save-dev @nomicfoundation/hardhat-toolbox
```

hardhat.config.jsに`require("@nomicfoundation/hardhat-toolbox");`を追加します。
```js
require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.9",
};
```

以上でプロジェクトの設定は完了です。

### さっそくスマートコントラクトを書いてコンパイルする
contract用のディレクトリ作成し、Token.solを作成します
```shell
mkdir contracts
touch Token.sol
```

書いてみると言ってみたもののコピペです。
説明文のコメントアウト省いて記載しておきます。

```solidity
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

contract Token {
    string public name = "My Hardhat Token";
    string public symbol = "MHT";

    uint256 public totalSupply = 1000000;

    address public owner;

    mapping(address => uint256) balances;

    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    constructor() {
        balances[msg.sender] = totalSupply;
        owner = msg.sender;
    }

    function transfer(address to, uint256 amount) external {   
        require(balances[msg.sender] >= amount, "Not enough tokens");

        balances[msg.sender] -= amount;
        balances[to] += amount;

        emit Transfer(msg.sender, to, amount);
    }

    function balanceOf(address account) external view returns (uint256) {
        return balances[account];
    }
}
```
コンパイルして成功するとartifactsというディレクトリが生成されます
```shell
npx hardhat compile
Compiled 1 Solidity file successfully
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263661/7cd68534-4167-3651-b516-b15f7ebb7b43.png)

スマートコントラクトが正しく動作するか確認するためにテストコードを書いていきます。
まずはテスト用のディレクトリとファイルを作成します。
テストはOwnerがトークンの最大供給量を保有しているかを確認しています。
```shell
mkdir contracts
touch Token.sol
```
Token.js
```javascript
const { expect } = require("chai");
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

describe("Token contract", function () {
    async function deployTokenFixture() {
        const Token = await ethers.getContractFactory("Token");
        const [owner, addr1, addr2] = await ethers.getSigners();
        const hardhatToken = await Token.deploy();

        await hardhatToken.deployed();

        return { Token, hardhatToken, owner, addr1, addr2 };
    }

    describe("Deployment", function () {
        it("Should set the right owner", async function () {
            const { hardhatToken, owner } = await loadFixture(deployTokenFixture);
            expect(await hardhatToken.owner()).to.equal(owner.address);
        });

        it("Should assign the total supply of tokens to the owner", async function () {
            const { hardhatToken, owner } = await loadFixture(deployTokenFixture);
            const ownerBalance = await hardhatToken.balanceOf(owner.address);
            expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
        });
    });

    describe("Transactions", function () {
        it("Should transfer tokens between accounts", async function () {
            const { hardhatToken, owner, addr1, addr2 } = await loadFixture(
                deployTokenFixture
            );

            await expect(
                hardhatToken.transfer(addr1.address, 50)
            ).to.changeTokenBalances(hardhatToken, [owner, addr1], [-50, 50]);

            await expect(
                hardhatToken.connect(addr1).transfer(addr2.address, 50)
            ).to.changeTokenBalances(hardhatToken, [addr1, addr2], [-50, 50]);
        });

        it("should emit Transfer events", async function () {
            const { hardhatToken, owner, addr1, addr2 } = await loadFixture(
                deployTokenFixture
            );

            await expect(hardhatToken.transfer(addr1.address, 50))
                .to.emit(hardhatToken, "Transfer")
                .withArgs(owner.address, addr1.address, 50);

            await expect(hardhatToken.connect(addr1).transfer(addr2.address, 50))
                .to.emit(hardhatToken, "Transfer")
                .withArgs(addr1.address, addr2.address, 50);
        });

        it("Should fail if sender doesn't have enough tokens", async function () {
            const { hardhatToken, owner, addr1 } = await loadFixture(
                deployTokenFixture
            );
            const initialOwnerBalance = await hardhatToken.balanceOf(owner.address);

            await expect(
                hardhatToken.connect(addr1).transfer(owner.address, 1)
            ).to.be.revertedWith("Not enough tokens");

            expect(await hardhatToken.balanceOf(owner.address)).to.equal(
                initialOwnerBalance
            );
        });
    });
});
```

これでテストを実行すると成功します。
```shell
npx hardhat test
Compiled 1 Solidity file successfully


  Token contract
    Deployment
      ✔ Should set the right owner (638ms)
      ✔ Should assign the total supply of tokens to the owner
    Transactions
      ✔ Should transfer tokens between accounts (67ms)
      ✔ should emit Transfer events
      ✔ Should fail if sender doesn't have enough tokens


  5 passing (775ms)
```

テストまでできるようになりました。
これで前半は終わりです。