VSCodeでSpring+Hardhat

# はじめに

サーバーから、定型のスマートコントラクトをデプロイするために
JavaのサーバーはSpring、スマートコントラクト開発はHardhatという環境を作成した備忘録です。

VSCodeにはあらかじめ、JavaやSpringのプラグインがインストールされていることを想定しています。

また、Nodeがインストールされていることを想定しています。

# Spring

VSCodeでSpringプロジェクトを作成します。

`コマンドパレット...`から、`Spring Initializr: Create a Maven Project...`を選択してプロジェクトを作成します。

:::note alert
Gradle+Hardhatが上手く動きませんでした。（原因不明）
:::

設定値は環境に合わせて変えてください。
以下は、サンプルで利用した設定値となります。

|項目|設定値|
|:--|:--|
|`Specify Spring Boot version.`|2.7.13|
|`Specify project language.`|java|
|`Input Group id`|com.example|
|`Input Artifact Id`|springhardhat|
|`Sprcify packaging type.`|Jar|
|`Sprcify java version.`|11|
|`Choose dependencies`|LombokとSpring Web|

`Choose dependencies`は、`Thymeleaf`や`Validation`や`Spring Security`などを設定するのが一般的だと思います。

最後に保存先のフォルダを選択するダイアログが出るので適当な場所を選択します。

フォルダ構成は以下のようになります。
`target`はないかもしれませんが、しばらく経つと出現します。

```txt
springhardhat
├─.mvn
├─.vscode
├─src
├─target
├─.gitignore
├─HELP.md
├─mvnw
├─mvnw.cmd
└─pom.xml
```

# Hardhat


## インストール

初期化とhardhatと必要なライブライをインストールします。

```sh
npm init -y
npm install --save-dev hardhat
npm install --save-dev @openzeppelin/contracts
npm install --save-dev @nomicfoundation/hardhat-toolbox
```

※：環境により、まあまあ時間がかかります。

## プロジェクトの作成

以下のコマンドでプロジェクトの作成がはじまります。

```sh
npx hardhat
```

`Create a JavaScript project`を選びJavaScriptのプロジェクトを選択します。


```console
888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

Welcome to Hardhat v2.16.1

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

`Hardhat project root:`は、Springプロジェクトと同じにします。
`Do you want to add a .gitignore?`も追記してほしいので、`Y`を選択します。


```console
888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

Welcome to Hardhat v2.16.1

√ What do you want to do? · Create a JavaScript project
√ Hardhat project root: · C:\\xxxxxxxxxxxxxxxxxxx\\springhardhat
√ Do you want to add a .gitignore? (Y/n) · y

You need to install these dependencies to run the sample project:
  npm install --save-dev "hardhat@^2.16.1" "@nomicfoundation/hardhat-toolbox@^3.0.0"

Project created

See the README.md file for some example tasks you can run

Give Hardhat a star on Github if you're enjoying it!

     https://github.com/NomicFoundation/hardhat
```

フォルダ構成は以下となります。
フォルダとして、`contracts` `node_modules` `scripts` `test`が増追加されています。。
ファイルは、`hardhat.config.js` `package-lock.json` `package.json` `README.md` が追加されています。
また、`.gitignore`　にはHardhat関連のフォルダが追記されています。

```txt
springhardhat
├─.mvn
├─.vscode
├─contracts
├─node_modules
├─scripts
├─src
├─target
├─test
├─.gitignore
├─hardhat.config.js
├─HELP.md
├─mvnw
├─mvnw.cmd
├─package-lock.json
├─package.json
├─pom.xml
└─README.md
```

## 環境ファイル設定

テスト用にHardhatの環境ファイル`hardhat.config.js`を設定します。
内容はアカウントを４つ明示的に追加しています。

```javascript:hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.18",
  networks: {
    hardhat: {
      accounts: [
        {
          privateKey:
            "0x0000000000000000000000000000000000000000000000000000000000000001",
          balance: "10000000000000000000000",
        },
        {
          privateKey:
            "0x0000000000000000000000000000000000000000000000000000000000000002",
          balance: "10000000000000000000000",
        },
        {
          privateKey:
            "0x0000000000000000000000000000000000000000000000000000000000000003",
          balance: "10000000000000000000000",
        },
        {
          privateKey:
            "0x0000000000000000000000000000000000000000000000000000000000000004",
          balance: "10000000000000000000000",
        },
      ],
    },
  },

};
```

## コントラクト作成

簡単なERC20を作成します。
`contracts`フォルダ配下に`Token.sol`というコントラクトを作成します。

```javascript:Token.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7 <0.9.0;

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

contract Token is ERC20, Ownable {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

    function mint(address account, uint256 amount) public onlyOwner {
        _mint(account, amount);
    }

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

```

テストコードも作成します。
`test`フォルダ配下に`Token.js`を作成します。

```javascript:Token.js
const {
    loadFixture,
} = require("@nomicfoundation/hardhat-network-helpers");
const { expect } = require("chai");

describe("Token", function () {
    async function deployContract() {
        const [owner, user1] = await ethers.getSigners();
        const Contract = await ethers.getContractFactory("Token");
        const name = "test token";
        const symbol = "tts";
        const contract = await Contract.deploy(name, symbol);
        return { contract, name, symbol, owner, user1, };
    }
    describe("Deployment", function () {
        it("Check name and symbol", async function () {
            const { contract, name, symbol, owner, user1 } = await loadFixture(deployContract);
            expect(await contract.name()).to.equal(name);
            expect(await contract.symbol()).to.equal(symbol);
        });
        it("mint and burn", async function () {
            const { contract, name, symbol, owner, user1 } = await loadFixture(deployContract);
            let mintAmount = 10000;
            let burnAmount = 5000;
            expect(await contract.balanceOf(user1.address)).to.equal(0);
            await contract.mint(user1.address, mintAmount);
            expect(await contract.balanceOf(user1.address)).to.equal(mintAmount);
            await contract.burn(user1.address, burnAmount);
            expect(await contract.balanceOf(user1.address)).to.equal(mintAmount - burnAmount);
        });
    });
});
```

テスト実行は以下のコマンドとなります。

```console
npx hardhat test test\Token.js
```

問題がなければ以下のような結果になります。

```console
 npx hardhat test test\Token.js


  Token
    Deployment
      ✔ Check name and symbol (1017ms)
      ✔ mint and burn


  2 passing (1s)
```

# Solidity→Java

Solidity（`Token.sol`)をJavaのコードに変換します。

## 準備

### Solidityのコンパイラー

Solidityのコンパイラーをダウンロードします。
https://github.com/ethereum/solidity/releases

ダウンロードしたら、適当なフォルダに配置します。
ここでは、`C:\tools\`に展開しています。

### web3j-cli（Java変換）

web3j-cliをダウンロードします。
https://github.com/web3j/web3j-cli/releases

:::note alert
ここではJava11を利用している為、1.4.2をダウンロードしています。
現在、1.5.0がリリースされており、Java17の場合はそちらを利用した方が良さそうです。（未検証）
:::

ダウンロードしたら、適当なフォルダに配置します。
ここでは、`C:\tools\`に展開しています。

### 出力フォルダ（コンパイル時）

出力フォルダを`out`としています。（特にフォルダ生成する必要はありません。）
一時フォルダなので、`.gitignore`に追記します。

### pom.xml追記

`pom.xml`の`<dependencies>`タグの中に以下の`<dependency>`（２つ）記述します。

```xml:pom.xml
		<!-- https://mvnrepository.com/artifact/org.web3j/core -->
		<dependency>
			<groupId>org.web3j</groupId>
			<artifactId>core</artifactId>
			<version>4.9.8</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.web3j/contracts -->
		<dependency>
			<groupId>org.web3j</groupId>
			<artifactId>contracts</artifactId>
			<version>4.9.8</version>
		</dependency>
```

:::note alert
ここでは、web3j-cliが1.4.2を選択している為、4.9.x系の最新を設定しています。
web3j-cliが1.5.0の場合は4.10.x系を指定した方がよさそうです。（未検証）
:::

### batchファイル作成

SolidityをJavaに変換するbatchファイルを作成します。
基本的には、Solidityをコンパイルしてコンパイル結果からJavaコードへ変換するという流れになります。

```winbatch:soltojava.bat
@echo off
@REM https://github.com/ethereum/solidity/releases
set SOLC=C:\tools\solc\bin\solc-windows.exe
@REM https://github.com/web3j/web3j-cli/releases
set WEB3J=C:\tools\web3j-1.4.2\bin\web3j.bat
set CONTRACT_NAME=Token
set JAVA_PACKAGE=com.example.springhardhat.contracts

call %SOLC% --version
call %WEB3J% -version

pushd %~dp0

call rmdir /s /q .\out

call %SOLC% ./contracts/%CONTRACT_NAME%.sol --bin --abi --optimize -o ./out --base-path ./contracts --include-path ./node_modules/

call %WEB3J% generate solidity -b ./out/%CONTRACT_NAME%.bin -a ./out/%CONTRACT_NAME%.abi -o ./src/main/java -p %JAVA_PACKAGE%
```

### Solidity→Java変換

Batchファイルを実行します。

```console
soltojava.bat
```

以下のような結果となれば変換が完了しています。

```console
solc, the solidity compiler commandline interface
Version: 0.8.20+commit.a1b79de6.Windows.msvc
              _      _____ _ 
             | |    |____ (_)
__      _____| |__      / /_
\ \ /\ / / _ \ '_ \     \ \ |
 \ V  V /  __/ |_) |.___/ / |
  \_/\_/ \___|_.__/ \____/| |
                         _/ |
                        |__/
by Web3Labs
Version: 1.4.2
Build timestamp: 2022-10-21 08:18:21.49 UTC
Compiler run successful. Artifact(s) can be found in directory "./out".
              _      _____ _ 
             | |    |____ (_)
__      _____| |__      / /_
\ \ /\ / / _ \ '_ \     \ \ |
 \ V  V /  __/ |_) |.___/ / |
  \_/\_/ \___|_.__/ \____/| |
                         _/ |
                        |__/
by Web3Labs
Generating com.example.springhardhat.contracts.Token ... File written to .\src\main\java
```
`src\main\java\com\example\springhardhat\contracts`配下に`Token.java`が生成されます。
大きなファイルなのでソースの掲載は割愛します。

### テストコード作成

以下はテストコードです。

```java:TokenTests.java
package com.example.springhardhat;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.math.BigInteger;

import org.junit.jupiter.api.Test;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.gas.ContractGasProvider;
import org.web3j.tx.gas.StaticGasProvider;

import com.example.springhardhat.contracts.Token;

public class TokenTests {

    @Test
    void checkNameAndSymbol() throws Exception {
        // デプロイ
        String url = "http://127.0.0.1:8545/";
        Web3j web3j = Web3j.build(new HttpService(url));
        Credentials credentials = Credentials
                .create("0x0000000000000000000000000000000000000000000000000000000000000001");
        BigInteger gasPrice = BigInteger.valueOf(1000000000);
        BigInteger gasLimit = BigInteger.valueOf(30000000);
        ContractGasProvider contractGasProvider = new StaticGasProvider(gasPrice, gasLimit);
        // 名称とシンボル
        String name = "java test coin";
        String symbol = "jtc";
        Token token = Token.deploy(web3j, credentials, contractGasProvider, name, symbol).send();
        assertEquals(token.name().send(), name);
        assertEquals(token.symbol().send(), symbol);
    }

    @Test
    void checkMintAndBurn() throws Exception {
        // デプロイ
        String url = "http://127.0.0.1:8545/";
        Web3j web3j = Web3j.build(new HttpService(url));
        Credentials credentials = Credentials
                .create("0x0000000000000000000000000000000000000000000000000000000000000001");
        BigInteger gasPrice = BigInteger.valueOf(1000000000);
        BigInteger gasLimit = BigInteger.valueOf(30000000);
        ContractGasProvider contractGasProvider = new StaticGasProvider(gasPrice, gasLimit);
        String name = "java test coin";
        String symbol = "jtc";
        Token token = Token.deploy(web3j, credentials, contractGasProvider, name, symbol).send();
        String contractAddress = token.getContractAddress();
        // トークンの発行と破棄
        Token token2 = Token.load(contractAddress, web3j, credentials, contractGasProvider);
        Credentials credentials2 = Credentials
                .create("0x0000000000000000000000000000000000000000000000000000000000000002");
        BigInteger mintValue = BigInteger.valueOf(100000);
        BigInteger burnValue = BigInteger.valueOf(40000);
        BigInteger balanceOf1 = token2.balanceOf(credentials2.getAddress()).send();
        assertEquals(balanceOf1, BigInteger.ZERO);
        token2.mint(credentials2.getAddress(), mintValue).send();
        BigInteger balanceOf2 = token2.balanceOf(credentials2.getAddress()).send();
        assertEquals(balanceOf2, mintValue);
        token2.burn(credentials2.getAddress(), burnValue).send();
        BigInteger balanceOf3 = token2.balanceOf(credentials2.getAddress()).send();
        assertEquals(balanceOf3, mintValue.subtract(burnValue));
    }

}
```

### テスト実行

JUnitでテストを実行する前に、以下のコマンドでノードを起動しておきます。

```console
npx hardhat node
```

ノードが起動したら、テストを実行します。

# まとめ

これで、Javaからいくらでもデプロイが出来ます。
が、秘密鍵をどう管理していくのか・・・そのあたりが重要なきもします。

今回のコードは以下のGithubにあります。

https://github.com/tnakagawa/springhardhat