🎩

Ethereumでdappsを作る第一歩!ローカルネットでのデプロイの方法

2022/02/13に公開

はじめに

記事の目的

ブロックチェーンを触りたいなと思っている人の目的の一つにcryptokittiesのようなdappを作りたいという願いがあると思います。(私もその一人です!)
 今回の記事は、その第一歩として、ローカルネットでのデプロイの方法を紹介したいと思います。なぜローカルネットでデプロイするのかや、ローカルネットでデプロイした後に待っている工程についても解説したいと思います!

記事で取り上げている手法(わかっていれば、記事を読む必要はないと思います)

現在、ローカルネットで、dappをデプロイをする方法は、主に二種類あると思っています。一つは、dapp universityなどで使われているtruffle(トリュフ)とganache(ガナッシュ)を使った方法、もう一つはbuildspaceなどで使われているhardhatを使った方法です
今回は、前者の方法よりも私は簡単だと思う、後者のhardhatを使った方法を紹介します!

この記事の読み方

初心者の方はこのコードを実際に実行しながら、この記事を読んでみてください。コードもコピペせずに書き写しながらやると、何がわかっていなくて何をわかっているかが理解できると思います!
今回は、buildspaceのBuild a Web3 App with Solidity + Ethereum Smart Contractsを参考にしながら書いているので、この記事を読んでから、buildspaceを見てもらえれば、更に理解が深まると思います。

この記事を読む前に必要な知識

  • 簡単なコマンドの使い方(mkdirやcdが使えればok!)
  • solidityの基本的な記法(わからない方は、cryptozombiesで基本を学んでください、そことのズレはこの記事で補足します!)
  • javascriptの基本的な記法(非同期処理がわかっていれば大丈夫です!progateではこれは解説してないので、おすすめはjavascript-primerです。)

なぜ、ローカルネットワークにデプロイするのか

いきなり本番環境でやったほうがいいのではないか?二度手間じゃないかと思う方もいると思います。
まず、メインネットにデプロイしてしっかり動くのかと実験しようとすると、ガス代といわれる料金が取られる上に、一度ブロックチェーンに上げたコードは変更できず、もしコードが間違っており変更しなければいけないとなったら、変更ではなく、もう一回デプロイしなければならず、お金もかかる上に、電子ゴミが溜まり環境にも良くないです。
少し詳しい方は、最初からテストネットに上げればよいという方もいるとは思うのですが、solidityのコードのチェックだけなら、ローカルネットでやったほうが手間はかからないと思います。

下準備(setup)

まずは、好きな場所に(僕はディレクトリを忘れにくくいつでも消しやすくするためにデスクトップにしてます)、ディレクトリをつくって、Node.jsプロジェクトとして初期化して、Hardhatをインストールします
 この時点で、node/npmをインストールしてない場合は、Node.jsのセットアップのやり方 を参考にインストールしてください。余談ですが、npm理解するために読んだ【初心者向け】NPMとpackage.jsonを概念的に理解する良かったです。

console
mkdir ファイル名
cd ファイル名
npm init -y
npm install --save-dev hardahat

npm init -y でnode.jsを初期化した状態
npm init -y でnode.jsプロジェクトとして初期化した状態

hardhatをインストールした状態
package.jsonにhardhatが追加され、package-lock.jsonが増えていることがわかります。

これらのインストールが終わったら、hardhatの開発環境の初期化と必要なファイルを用意してもらいます。

console
npx hardhat

このコマンドを入力して、create a basic sampleを選ぶと以下のようになり、必要なファイルがインストールされます。(gitignoreが必要かなど聞かれると思いますがとりあえずyesで大丈夫です!)

hardhatが必要なファイルを用意してくれる

他にも、必要なパッケージがあればインストールします。(npx hardhatの前でもいつでも大丈夫です!)

console
npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers


devDependenciesを見れば、色々とインストールされたことがわかります

[コラム]hardhatはEthereumのアカウント取得が簡単

hardhatでは、npx hardhat accountsとコマンドを打つだけで、hardhatがアドレスを用意してくれます。truffleとganacheを使うとこの環境構築が意外としんどかったです。もちろん自分のタイプミスだったと思うのですが、この取得をするのにエラーが出て前に進めなくなった経験があります。(最初からやり直すことでできたにはできたのですが...)
hardhatならそういう苦労は少なくともなくて快適です!

サンプルプロジェクトを動かしてみよう!

コンパイル

コードを機械が読めるように翻訳します

console
npx hardhat compile


artifactsとcacheというディレクトリが増えました。

テスト

console
npx hardhat test

このコマンドを打つだけでいけます。
これは驚くべきことです!先程も触れましたが、truffleやGanacheを使っていてはこうは行きません。この驚きを的確に指摘されている言葉があったので引用させてもらいます。

hardhatでのコントラクトのテストコードと実行結果を一番最初に目の当たりにしたとき、接続先ノードの設定や、トランザクションの発行を行うためのアカウントの秘密鍵等の設定がどこにも明示的に書かれていないにもかかわらず、テストが動くことにとても驚きました。コントラクトの単体テスト用の環境を、hardhatコマンドが全て良い感じに暗黙的に解決してくれているようです。

Ethereum コントラクト開発 Hello world ローカル編より

これでサンプルプロジェクトを動かせましたね!

コードの実装

次は、サンプルプロジェクトではなくて、自分たちのコードを書いていきましょう。contracts,scripts.testのディレクトリ以下にあるファイルを全部消して、自分で書いていきましょう。

solidityファイル

まずは、contractsディレクトリの下に、solidityファイルを用意しましょう。

WavePortal.sol
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract WavePortal{
    constructor(){
        console.log("Yo,yo! I am normal smart contract");
    }
}

少しだけ、コードを解説します。(基本はcryptozombiesで確認してください)

WavePortal.sol
//SPDX-License-Identifier: Unlicense

最近必要になったらしいですね。これがないと、vs codeが許してくれません。

WavePortal.sol
pragma solidity ^0.8.0;

コンパイラに自分たちが使うsolidityのversionを伝えるためのものです。hardhat.config.jsと食い違ってないか確認が必要です。ちなみにここのversionが古いとvs codeはエラーを出してくる。(dapp universityなど情報が古いコードをコピペすると、このversionが古い)

WavePortal.sol
import "hardhat/console.sol";

hardhatが色々助けてくれるらしいです。デバックしやすくなったりするらしいです。

run.jsファイル

これでスマートコントラクトは書けました!
あとはこのsolidityファイルをコンパイルして、local blockchainにデプロイすると、コンソールログの内容が実行されるはずです!

run.jsを用意する理由

先程のように、このコントラクトをコンパイルして動かしてもいいのですが、solidityの特徴はblockchainやethereum walletとinteractできることにあるのだから、local blockchain上で実際のblockchainに似せた環境を用意してあげるほうがいいということで、run.jsを用意します。
run.jsを使って、コンパイルし、デプロイし、動かしてテストします

run.jsを書く

scriptsディレクトリの下にrun.jsファイルを作りましょう。

run.js
const main = async () => {
    const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
    const waveContract = await waveContractFactory.deploy();
    await waveContract.deployed();
    console.log("Contract deployed to:",waveContract.address);
};
const runMain = async () => {
    try{
        await main();
        process.exit(0);
    } catch (error){
        console.log(error);
        process.exit(1);
    }
};

runMain();

hardhat特有のコードがあるので、紹介します!

run.js
const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");

コントラクトをコンパイルして、artifactsディレクトリの下に必要なファイルを生成してくれます。
hreはhardhat特有のobjectで、npx hardhat とさえコマンドをかけば、importしなくても、hardhat.config.jsから読み込んでくるみたいです

run.js
const waveContract = await waveContractFactory.deploy();

hardhatがこのコントラクトのためだけに、local ethereum networkを作ってくれます。そして、それはスクリプトが完了した後破壊されます!コントラクトを実行するたびに毎回新しいブロックチェーンができ、常にまっさらな状態から始まるので、エラーのデバックがしやすいです!

run.js
await waveContract.deployed();

コントラクトがローカルのブロックチェーンに正式にデプロイされるまで待ちます

run.js
console.log("Contract deployed to:", waveContract.address);

デプロイされると waveContract.address は、デプロイされたコントラクトのアドレスを教えてくれます。このアドレスは、ブロックチェーン上で実際に私たちのコントラクトを見つけることができる方法です。実際のブロックチェーン上には、何百万ものコントラクトが存在します。つまり、このアドレスがあれば、作業したいコントラクトに簡単にアクセスできるようになります。これは後ほど、実際のEthereumネットワークにデプロイしたときに、より重要になります。

asyncやawaitがわかってない人は非同期処理が、tryやcatchがわかっていない人は例外処理がわかっていないと思うので、おすすめはJsvaScript Primerを読んでみてください。(progateにはなかったです。)
process.exit()はNode.jsプログラムをプログラムで終了できる便利なメソッドです。0は成功を意味していいて、終了コードが異なれば意味が異なります。1は、uncaguht fatal exceptionを意味しています。
詳しくはNode.jsプログラムを終了する方法を見ればわかりやすいと思います。Node.js公式も見ておくといい思います。

最後に、run.jsを実行しましょう!

console
npx hardhat run scripts/run.js

まとめると、このrun.jsの役割は、smart contractをcompileして、deployして、executeするというのを高速でテストできるスクリプトということさえ抑えておけば大丈夫そうです。

functionの動作を試したいときのrun.js

WavePortal.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract WavePortal {
    uint256 totalWaves;

    constructor() {
        console.log("Yo yo, I am a contract and I am smart");
    }

    function wave() public {
        totalWaves += 1;
        console.log("%s has waved!", msg.sender);
    }

    function getTotalWaves() public view returns (uint256) {
        console.log("We have %d total waves!", totalWaves);
        return totalWaves;
    }
}

例えば、contractにこのように試したいfucntionを増やしたとしましょう。waveしたときのfunction、wave()と、waveが何回されてるか確認できるget function、 getTotalWaves()が追加されました
この場合、このfunctionが成り立つかテストするためのrun.jsは、例えばこうします

run.js
const main = async () => {
  const [owner, randomPerson] = await hre.ethers.getSigners();
  const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
  const waveContract = await waveContractFactory.deploy();
  await waveContract.deployed();

  console.log("Contract deployed to:", waveContract.address);
  console.log("Contract deployed by:", owner.address);

  let waveCount;
  waveCount = await waveContract.getTotalWaves();

  let waveTxn = await waveContract.wave();
  await waveTxn.wait();

  waveCount = await waveContract.getTotalWaves();
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

またhardhat特有のものが出てきたので解説します!

run.js
    const [owner, randomPerson] = await hre.ethers.getSigners();

ブロックチェーンに何かをデプロイするためには、ウォレットアドレスが必要です。hardhatは先程のテストのように、バックグラウンドで勝手にこれを行ってくれていますが、ここではコントラクト所有者のウォレットアドレスを取得し、さらにランダムなウォレットアドレスを取得してrandomPersonと名付けています。
この[]の書き方がわからない方は、これはjavascriptの分割代入という方法なので、調べてみてください。

run.js
    let waveCount;
    waveCount = await waveContract.getTotalWaves;

    let waveTxn = await waveContract.wave();
    await waveTxn.wait();

    waveCount = await waveContract.getTotalWaves();

手動でfunctionはcallしないといけません
この.wait()は何なのか調べてみたところ、hardhat公式によると、

wait until the transaction is mined

「トランザクションがマイニングされるまで待っている」みたいですね。

そして先程と同じようにrun.jsを実行しましょう!

console
npx hardhat run scripts/run.js

まとめ

まとめると、run.jsの機能は

  1. 新しいlocal Ethereum networkをつくり
  2. コントラクトをデプロイし
  3. スクリプトがおわると、local networkを破壊する
    で、使い方は上のとおりやればできるということがわかりました。

おまけ

ほかにも、ここで取得したrandam personをつかって

run.js
waveTxn = await waveContract.connect(randomPerson).wave();
await waveTxn.wait();

waveCount = await waveContract.getTotalWaves();

これをrun.jsに追加して、自分のアドレス以外にwave()を実行してもらったりと、色々できます。

ローカルでデプロイする

先程のrun.jsはテストだったため、作ったそばからlocal networkを壊していましたが、次はlocal network生かしたままでいきましょう

console
npx hardhat node

このコマンドを打つと、local networkが作られます。このterminal windowはこのまま放置して、新しいwindowで次からのコマンドを打ちましょう

deploy.js
const main = async () => {
    const [deployer] =await hre.ethers.getSigners();
    const accountBalance = await deployer.getBalance();
    
    console.log("Deploying contracts with accounts:", deployer.address);
    console.log("Account balance:" ,accountBalance.toString());

    const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
    const waveContract =await waveContractFactory.deploy();
    await waveContract.deployed();

    console.log("WavePortal address:", waveContract.address);
};

const runMain = async () => {
    try{
        await main();
        process.exit(0);
    }catch (error){
        console.log(error);
        process.exit(1);
    }
}

runMain();

scriptsディレクトリに、上のファイルを作成して

console
npx hardhat run scripts/deploy.js --network localhost

これで、コントラクトをデプロイし、ブロックチェーン上にあるこのプロジェクトのアドレスがわかりました。

これにて、ローカルネットでのデプロイ完了です。
先程、ローカルネットを作ったターミナルに下の写真のような物が写っていると思います

ローカルネットにデプロイした後

当然、これだけでは、実際のブロックチェーン上で動くdappにはなりません。
このあと、テストネットで運用してみて、その後メインネットにデプロイという流れを取ります。
今回はローカルネットをまとめるだけで、疲れてしまったので、また今後の動きは次回以降にまとめたいと思います!

最後に

初心者から、blockchainの世界に入るには、覚えることがたくさんあると思います、今回は扱いませんでしたが、フロントを作ろうとすれば、htmlやcss、reactなど、blockchain周りの技術としては、solidityやweb3.jsなど。
僕も、まだまだ知らないことだらけですが、この技術を触り始めてから毎日が充実しています!なので、ブロックチェーンを楽しみながら毎日を過ごしていきましょう!
僕も、一緒に楽しめる仲間が欲しいので、この記事の感想でも、わかりにくかった部分でも、ぜんぜん違う話題でもいいです。twitterにDM待ってます!では

次に調べたいと思っていること

  • ethers.js

resource(上記の内容でわかりにくかった人はこのあたりも見るといいかも)

buildspace: 今回一番参考にした、これの解説書と言っても過言ではない
dapp university: truffleとGanacheを使った解説
【完全版】まだTruffleで消耗してるの?Solidity開発ツール「Hardhat」の概要やメリット・使い方について徹底解説: hardhatのほうが使いやすいってこと教えてくれた記事
Hardhat + Solidityでスマコン動かしてみた_チュートリアル編: hardhatの公式を簡単に砕いてくれてる

Discussion