加具留矢流余

かぐるやるよ

Truffleを使ったSolidity開発環境の構築 + FaucetのExampleの作成

前回マスタリングイーサリアムを読んでイーサリアム周辺の話をざっくり勉強したので今度は実践。ソースコードは↓

github.com

やること

truffleとganacheを導入してローカルでsolidityをコンパイル+実行できる環境を整える。全体の流れとしては、シンプルなFaucetコントラクトを作成→ローカルのシミュレーターにデプロイ→javascriptからweb3jsを使ってABIを叩く(ABIを叩くという表現であってる?)。結構な分量になってしまった。

環境のセットアップ

何はともあれganacheをインストールする。ganacheはローカルのブロックチェーンシミュレーターで、作ったコントラクトを気軽にデプロイして試すことができる。ganache-cliというcli版も存在するのでお好きな方を。インストールが完了したら立ち上げてQuick Startを押すとシミュレーターが動き出す。

trufflesuite.com

ganacheのインストールが終わったら作業用のフォルダを作成。ついでにgitも初期化しておく。

$ mkdir example1
$ cd example1
$ git init

npmで必要なパッケージをインストールする。openzeppelinは今回は利用しないがコントラクト開発で必要不可欠と言っても過言ではないのでインストール。

$ npm install web3
$ npm install -D truffle @openzeppelin/contracts mocha 

ここまでで主要なツール群はインストールが終わったのでtruffleプロジェクトの初期化を行う

$ npx truffle init

実行するとディレクトリ内にcontracts, migrations, test, truffle-config.jsなどのディレクトリやファイルが作成される。truffle-config.jsにネットワークの定義が記載されているので必要に応じてganache側の設定と合わせる。デフォルトでdevelopmentと言うネットワークがコメントアウトされているので、コメントを外してポート番号もganacheのものに合わせておく。

  networks: {
    development: {
      host: "127.0.0.1",     // Localhost (default: none)
      port: 8545,            // Standard Ethereum port (default: none)
      network_id: "*",       // Any network (default: none)
    }

Solidityソースコードの作成

今回は誰でもイーサの預け入れと引き出しができる口座を作成する。蛇口のように引き出せるからFaucetと呼ばれるらしい。contracts以下に新たにFaucet.solを作成し以下のようにコードを記述する。はてぶがsolidityに対応していてびっくり。

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Faucet {

    // state variables (訳が分からない状態変数でOK?)
    address owner;

    // event類
    event WithDraw(address indexed, uint amount);
    event Deposit(address indexed, uint amount);

    constructor() {
        // 作成者を記録
        owner = msg.sender;
    }

    // 指定した額を送金する関数. soliditiyにおける通貨の基本単位はweiであることに注意
    // msg.senderに対して明示的にpayableであることを指定する必要あり
    function withDraw(uint withdraw_amount) public {
        require(withdraw_amount <= 0.01 ether);
        // transferメソッドはaddress paybale型にしか存在しないためキャストが必要になる
        payable(msg.sender).transfer(withdraw_amount);
        emit WithDraw(msg.sender, withdraw_amount);
    }

    // Mastering Etherium出版後からsolidityの文法に変更が加わっている.
    // 0.6以降はデフォルトの明示的な指定が必要となるためreceive関数
    // payable修飾子を付けて置けば、そのメソッドに対して送金された場合に
    // 送金を受け付けることができる.逆に明示的に指定しない限りコントラクトが
    // 送金を受け取ることができない.
    receive() external payable {
        emit Deposit(msg.sender, msg.value); 
    } 
}

Faucetの概要はおおよそコメントに書いた通り。マスタリングイーサリアムのバージョン0.4と0.8では文法が変わっていたりするので注意。

コードが書けたら試しにコンパイルする。コンパイルされた結果として出力されるABIやEVMバイトコードはbuild以下にjson形式で格納される。

$ npx truffle compile 

デプロイ用のマイグレーションファイルを作成する

migrations以下にデプロイ用のjsスクリプトを作成する。initしたタイミングで1_initial_migration.jsが作成されるが、それをコピーして2_deploy_contract.jsを作成する。

const Faucet = artifacts.require("Faucet");

module.exports = function (deployer) {
  deployer.deploy(Faucet);
};

requireしているファイルがMigraionからFaucetに変更している点に注意。マイグレーションファイルが作成できたら実際にデプロイする。

$ npx truffle migrate

成功すれば画像のように実行結果が表示される。これで無事ganache上のテストネットワークにコントラクトがデプロイされた。

f:id:theflyingcat28:20211218225426p:plain
migrationの実行結果

Web3jsでコントラクトを叩く

折角コントラクトを作ったので実際に利用してみる。truffleには実行補助用のコマンドexecがあるので、それを利用して動作するjavascriptコードを作成する。今回はプロジェクトルート直下にmain.jsを作成した。

trufflesuite.com

web3jsを使って作成したコントラクトに入金した上で、所有する各アカウントから0.001etherづつ引き出すコードを作成した。web3jsの使い方は省略するが、ABIとアドレスを指定してContractインスタンスを作成して、methods.関数名.sendまたはmethods.関数名.callで呼び出す。どちらを使うかはブロックチェーン上のデータを変更するかどうかによって使い分ける。

const Faucet = require("./build/contracts/Faucet.json");

async function printBalance(address) {
    let balance = await web3.eth.getBalance(address);
    console.log(`Current Contract Balance: ${balance}`);
}

module.exports = async (callback) => {
    // 第1引数にデプロイしたコントラクトアドレスを指定する

    // truffle標準にいい感じで引数を取る方法が無さそうなのでprocess.argvから気合で取る
    const argv = process.argv.slice(4);
    const contractAddress = argv[0];
    console.log(`Contract Address: ${contractAddress}`);
    const faucet = new web3.eth.Contract(Faucet.abi, contractAddress);

    // Mainとなるアカウントを指定
    const accounts = await web3.eth.getAccounts();
    const mainAccount = accounts[0];

    // メインアカウントからFaucetに1ether預け入れ
    console.log("[*] Deposit 1 ether")
    await printBalance(contractAddress);
    await web3.eth.sendTransaction({
        from: mainAccount,
        to: contractAddress,
        value: web3.utils.toWei("0.1", 'ether')
    });
    await printBalance(contractAddress);
    console.log("");

    console.log("[*] Withdraw from each account.")
    await printBalance(contractAddress);
    for(let account of accounts) {
        await faucet.methods.withDraw(web3.utils.toWei("0.001", "ether")).send({
            from: account
        });
    }
    await printBalance(contractAddress);
}

実行するにはtruffle execを利用する。単位がweiのためわかりにくいがコントラクトの残高が変化していることが確認できた。

$ npx truffle exec main.js [コントラクトアドレス]
Using network 'development'.

Contract Address: 0xD1AE2406c079a003ffECb31c46b29cB63E37bAff
[*] Deposit 1 ether
Current Contract Balance: 4498000000000000000
Current Contract Balance: 4598000000000000000

[*] Withdraw from each account.
Current Contract Balance: 4598000000000000000
Current Contract Balance: 4588000000000000000

おわりに

ganache上でコントラクトを実行するまでの一連の流れを紹介した。内容に不備があればコメントください。

参考