概要
Ethereumを使ったスマートコントラクトの開発をしていて、ローカルの開発環境としてGanacheが便利だよ、と聴いたのでその入門ログを残します。
リアルタイムで導入しながら記事を書いています。たまにはこういう記事の書き方もいいですね。
前提
当方、メインネットへのスマートコントラクトのデプロイ(という言い方が正しいかどうかすらわからない)とかやったことないですし、入門書を読んで一通りやったくらいです。
これくらいのレベルの人間でも環境構築して動作させるくらいは訳ない、という意味でのGanache解説記事だと思っていてくださいm(_ _)m
導入手順
Google検索
「Ethereum Ganache」でGoogle検索すると
Ethereum のローカル開発環境 Ganache を使ってみる
Truffle Suite | Ganache - Truffle Framework
Ganache で始める Ðapp 開発
あたりが出てきました。
まあ公式を見るのが最善でしょう。ブロックチェーン領域はメジャーバージョンアップで過去のドキュメントが参考にならなくなることもしばしば。
公式のクイックスタートを見ながらやってみます。
Ganacheのダウンロード
GitHub ReleasesからDMGファイルを落とします。
次に、CLIをインストールします。と思ったのですがこれはデスクトップ版とCLI版がある、ということなんですね。せっかくなのでこちらも入れてみます。
npm install -g ganache-cli
+ ganache-cli@6.1.8
added 4 packages from 36 contributors in 32.249s
READMEによると、Dockerバージョンもあるみたいですね。
docker run -d -p 8545:8545 trufflesuite/ganache-cli:latest
CLIバージョンでスマートコントラクトの実行を試す
デスクトップ版を使っている解説記事が多いですが、ここではあえてCLIで。
ganache-cliコマンドを叩くと、それだけでGanacheが起動するようです。
$ ganache-cli
Ganache CLI v6.1.8 (ganache-core: 2.2.1)
Available Accounts
==================
(0) 0x6c96ae5a0710680aaa4c57b5683b2bf89343HOGE (~100 ETH)
(1) 0x85aa9d90973598ef2cf8f08a37a16421034aHOGE (~100 ETH)
・・・
Gas Price
==================
20000000000
Gas Limit
==================
6721975
Listening on 127.0.0.1:8545
本当かな?ということで試してみましょう。
まずは適当なスマートコントラクトを記述します。
pragma solidity ^0.4.18;
import "../node_modules/openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
contract DappsToken is StandardToken {
string public name = "DappsToken";
string public symbol = "DTKN";
uint public decimals = 18;
constructor(uint initialSupply) public {
totalSupply_ = initialSupply;
balances[msg.sender] = initialSupply;
}
}
次に、マイグレーションファイルを書きます。
var DappsToken = artifacts.require("./DappsToken.sol");
module.exports = function (deployer) {
var initialSupply = 1000;
deployer.deploy(DappsToken, initialSupply);
}
これらをそれぞれcontracts、migrationsディレクトリに入れて、
npm install -g truffle
truffle compile
truffle migrate
を叩きます。このあたりはTruffleの公式ドキュメントにも書かれているので、そちらを参照しながら進めてください。
Ganacheによってすでにローカル開発環境としてブロックチェーンが起動しているので、スマートコントラクトがデプロイされます。(デプロイって単語は合っているのかな)
Running migration: 2_deploy_dapps_token.js
Deploying DappsToken...
... 0xcab58c136b6bafe5c7eb7fa4aaHOGE42cb6589d93cd9fc62cfa99ae3598aHOGE
DappsToken: 0xd4f045b7615084aHOGE9441357203dd4e079HOGE
Saving successful migration to network...
... 0xd58206d125c10cb5f457a2de854bc2HOGE1d5819177f902c38c8b7b1396eHOGE
Saving artifacts...
次に、スマートコントラクトを実行できるプログラムを書きます。Node.jsを採用しました。余談ですがasync awaitはやっぱり便利ですね・・・。やっていることは、2者で送受金やっているだけです。
var Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
class web3Client {
constructor() {
this.ERC20_TOKEN = '0xd4f045b7615084aHOGE9441357203dd4e079HOGE';
}
get contract() {
return new web3.eth.Contract(this.minAbi, this.ERC20_TOKEN);
}
get minAbi() {
return [
// balanceOf
{
"constant": true,
"inputs": [{
"name": "_owner",
"type": "address"
}],
"name": "balanceOf",
"outputs": [{
"name": "balance",
"type": "uint256"
}],
"type": "function"
},
{
"constant": false,
"inputs": [{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [{
"name": "",
"type": "bool"
}],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
// decimals
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{
"name": "",
"type": "uint8"
}],
"type": "function"
}
];
}
async getBalance(account) {
return new Promise((resolve, reject) => {
this.contract.methods.balanceOf(account).call({
from: account
}, (error, balance) => {
resolve(balance);
});
});
}
async transfer(from, to, value = 1) {
return new Promise((resolve, reject) => {
this.contract.methods.transfer(to, value).send({
from
}, (error, transactionHash) => {
resolve(transactionHash);
});
})
}
}
const _main_ = async () => {
let balance = await new web3Client().getBalance('0x6c96ae5a0710680aaa4c57b5683b2bf89343HOGE');
console.log('送信者の残高', balance);
balance = await new web3Client().getBalance('0x85aa9d90973598ef2cf8f08a37a16421034aHOGE');
console.log('受信者の残高', balance);
const transferResult = await new web3Client().transfer('0x6c96ae5a0710680aaa4c57b5683b2bf89343HOGE', '0x85aa9d90973598ef2cf8f08a37a16421034aHOGE', 1);
console.log('送信に成功しました。トランザクション:', transferResult);
balance = await new web3Client().getBalance('0x6c96ae5a0710680aaa4c57b5683b2bf89343HOGE');
console.log('送信者の残高', balance);
balance = await new web3Client().getBalance('0x85aa9d90973598ef2cf8f08a37a16421034aHOGE');
console.log('受信者の残高', balance);
}
_main_();
ソースコード中のポイントを書いておきます。
Ganacheを起動したときに、8545ポートで起動したというコンソールへのログが出ていたので、それをそのまま書きます。
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
スマートコントラクトをデプロイしたときに、DappsToken: hogehogehoge...というログが出ていたと思うのですが、それをコピーして定数に入れます。
この辺はどう考えても環境変数に持たせたほうがいいので、実際に開発するときはそのあたりも考えてください。
this.ERC20_TOKEN = '0xd4f045b7615084aHOGE9441357203dd4e079HOGE';
あとはGanacheを起動したときに「Available Accounts」として出てきたアカウントから任意の2アカウントを選んで、getBalanceとtransfer関数の引数に渡すようにしています。
以上のソースコードを実行すると
$ node execContract.js
送信者の残高 1
受信者の残高 999
送信に成功しました。トランザクション: 0xcdcded20fa32c4e6754db630c05b25697a9b1309f58a012b6f7228fe546e7f0f
送信者の残高 0
受信者の残高 1000
このように、送金に成功していることがわかります。1000イーサ持っている方から送らせたほうが見栄えが良かったですがもう気にしない・・・
CLIに戻ってみると、ログが出ています。
eth_gasPrice
eth_sendTransaction
Transaction: 0xcdcded20fa32c4e6754db630c05b25697a9b1309f58a012b6f7228fe546e7f0f
Gas usage: 21547
Block Number: 9
Block Time: Wed Oct 24 2018 14:53:29 GMT+0900 (JST)
eth_getTransactionReceipt
まとめ
特にまとまりのない記事でしたが、見よう見まねで自分のPCでブロックチェーンを動かすことに成功しました。
以前Gethを使ってEthereumを動かしたことがあったのですが、コマンドは長いしマイニングも自分で実行しないといけないしで、かなり面倒だったところが、今回はコマンド一発でそちら側の構築が終わるというのはなかなかお手軽です。
Truffle側のドキュメントも目を通してみてますが、サンプルも豊富なので勉強が捗りそうです。