はじめに
サーバーから、定型のスマートコントラクトをデプロイするために
JavaのサーバーはSpring、スマートコントラクト開発はHardhatという環境を作成した備忘録です。
VSCodeにはあらかじめ、JavaやSpringのプラグインがインストールされていることを想定しています。
また、Nodeがインストールされていることを想定しています。
Spring
VSCodeでSpringプロジェクトを作成します。
コマンドパレット...
から、Spring Initializr: Create a Maven Project...
を選択してプロジェクトを作成します。
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
はないかもしれませんが、しばらく経つと出現します。
springhardhat
├─.mvn
├─.vscode
├─src
├─target
├─.gitignore
├─HELP.md
├─mvnw
├─mvnw.cmd
└─pom.xml
Hardhat
インストール
初期化とhardhatと必要なライブライをインストールします。
npm init -y
npm install --save-dev hardhat
npm install --save-dev @openzeppelin/contracts
npm install --save-dev @nomicfoundation/hardhat-toolbox
※:環境により、まあまあ時間がかかります。
プロジェクトの作成
以下のコマンドでプロジェクトの作成がはじまります。
npx hardhat
Create a JavaScript project
を選びJavaScriptのプロジェクトを選択します。
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
を選択します。
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関連のフォルダが追記されています。
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
を設定します。
内容はアカウントを4つ明示的に追加しています。
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
というコントラクトを作成します。
// 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
を作成します。
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);
});
});
});
テスト実行は以下のコマンドとなります。
npx hardhat test test\Token.js
問題がなければ以下のような結果になります。
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
ここではJava11を利用している為、1.4.2をダウンロードしています。
現在、1.5.0がリリースされており、Java17の場合はそちらを利用した方が良さそうです。(未検証)
ダウンロードしたら、適当なフォルダに配置します。
ここでは、C:\tools\
に展開しています。
出力フォルダ(コンパイル時)
出力フォルダをout
としています。(特にフォルダ生成する必要はありません。)
一時フォルダなので、.gitignore
に追記します。
pom.xml追記
pom.xml
の<dependencies>
タグの中に以下の<dependency>
(2つ)記述します。
<!-- 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>
ここでは、web3j-cliが1.4.2を選択している為、4.9.x系の最新を設定しています。
web3j-cliが1.5.0の場合は4.10.x系を指定した方がよさそうです。(未検証)
batchファイル作成
SolidityをJavaに変換するbatchファイルを作成します。
基本的には、Solidityをコンパイルしてコンパイル結果からJavaコードへ変換するという流れになります。
@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ファイルを実行します。
soltojava.bat
以下のような結果となれば変換が完了しています。
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
が生成されます。
大きなファイルなのでソースの掲載は割愛します。
テストコード作成
以下はテストコードです。
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でテストを実行する前に、以下のコマンドでノードを起動しておきます。
npx hardhat node
ノードが起動したら、テストを実行します。
まとめ
これで、Javaからいくらでもデプロイが出来ます。
が、秘密鍵をどう管理していくのか・・・そのあたりが重要なきもします。
今回のコードは以下のGithubにあります。