目次

Truffleを使ったスマートコントラクト開発

Truffle は ethereum スマートコントラクトのコンパイル、マイグレーション、テストを行うためのフレームワークです。

必要なソフトウエア

Truffle は npm を使ってインストールしますので、事前に npm をインストールしてください。

npm パッケージ配布サイト

また、ethereum ブロックチェーンも使用しますので ethereumブロックチェーンの構築(Ganache編) を参考に Ganache をインストールしてください。

インストール

> npm install -g truffle

初期化

> mkdir my_project
> cd my_project
> truffle init
 
 
Starting init...
================
 
> Copying project files to C:\Users\shinobu\NoNameSeminer\ethereum\my_project
 
Init successful, sweet!
 
Try our scaffold commands to get started:
  $ truffle create contract YourContractName # scaffold a contract
  $ truffle create test YourTestName         # scaffold a test
 
http://trufflesuite.com/docs

生成されたファイルを確認してみます。

> tree /f
ボリューム シリアル番号は EEAF-D230 です
C:.
│  truffle-config.js
│
├─contracts
│      Migrations.sol
│
├─migrations
│      1_initial_migration.js
│
└─test
        .gitkeep

各ファイルとディレクトリの説明。

設定

truffle-config.js を開いて、コメントを削除し以下のように変更してください。

  networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it's defined here and no other network is specified at the command line.
    // You should run a client (like ganache-cli, geth or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 7545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },

Ganacheとの連携

Ganache を起動し「NEW WORKSPACE」をクリックします。

「ADD PROJECT」をクリックし、先ほど作成した「truffle-config.js」を選択します。

これで Ganache と Truffle の連携設定は完了です。「SAVE WORKSPACE」をクリックしてください。

Truffle コンソール

> truffle console
truffle(development)>

スマートコントラクト

生成

truffle(development)> truffle create contract Counter
truffle(development)>

上記のコマンドで constracts ディレクトリの中に Counter.sol ファイルが生成されます。

実装

エディタで Counter.sol を開き、以下のように編集してください。

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
 
contract Counter {
  uint public count;
 
  // Function to get the current count
  function get() public view returns (uint) {
    return count;
  }
 
  // Function to increment count by 1
  function inc() public {
    count += 1;
  }
 
  // Function to decrement count by 1
  function dec() public {
    count -= 1;
  }
}

コンパイル

truffle(development)> truffle compile
 
Compiling your contracts...
===========================
> Compiling .\contracts\Counter.sol
> Compiling .\contracts\Counter.sol
> Compiling .\contracts\Migrations.sol
> Artifacts written to C:\Users\shinobu\NoNameSeminer\ethereum\my_project\build\contracts
> Compiled successfully using:
   - solc: 0.8.13+commit.abaa5c0e.Emscripten.clang
truffle(development)>

マイグレーションファイルの作成

Counter コントラクトをデプロイするためのマイグレーションファイルを作成します。

truffle(development)> truffle create migration Counter
truffle(development)>

上記のコマンドで migrations ディレクトリの中に nnnnnnnn_counter.js ファイルが生成されます。

ファイルの以下のように書き換えて、Counter コントラクトをデプロイするように設定します。

const Counter = artifacts.require("Counter");
module.exports = function(_deployer) {
  // Use deployer to state migration tasks.
  _deployer.deploy(Counter)
};

デプロイ

truffle(development)> truffle migrate
 
Compiling your contracts...
===========================
> Compiling .\contracts\Counter.sol
> Compiling .\contracts\Migrations.sol
> Artifacts written to C:\Users\shinobu\NoNameSeminer\ethereum\my_project\build\contracts
> Compiled successfully using:
   - solc: 0.8.13+commit.abaa5c0e.Emscripten.clang
 
 
Starting migrations...
======================
> Network name:    'development'
> Network id:      5777
> Block gas limit: 6721975 (0x6691b7)
 
 
1_initial_migration.js
======================
 
   Replacing 'Migrations'
   ----------------------
   > transaction hash:    0x09353f6ea3c2ce57acf8de3a9c2c1110fb2abfeea6d51d896477509d6e6edc6a
   > Blocks: 0            Seconds: 0
   > contract address:    0x9864601fA497E29d85720c336b15267Ff0a89386
   > block number:        1
   > block timestamp:     1651037552
   > account:             0x12C1B9B5152b14CE5af7A83947971108Dc89e54D
   > balance:             99.99502292
   > gas used:            248854 (0x3cc16)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00497708 ETH
 
   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00497708 ETH
 
 
1651036112_counter.js
=====================
 
   Replacing 'Counter'
   -------------------
   > transaction hash:    0x45b148c06609f587e5636a17a088f6685fc2e05603aed75a54bfcf01ba881129
   > Blocks: 0            Seconds: 0
   > contract address:    0x852560406282C788aeF29AbD5A9313588d86c3dB
   > block number:        3
   > block timestamp:     1651037554
   > account:             0x12C1B9B5152b14CE5af7A83947971108Dc89e54D
   > balance:             99.99086736
   > gas used:            165265 (0x28591)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.0033053 ETH
 
   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:           0.0033053 ETH
 
Summary
=======
> Total deployments:   2
> Final cost:          0.00828238 ETH
 
 
truffle(development)>

実行

Counter コントラクトを呼び出すためのインスタンスを取得する。

truffle(development)> let counter = await Counter.deployed()
undefined
truffle(development)>

Counter コントラクトの get を呼び出す。

truffle(development)> counter.get()
BN { negative: 0, words: [ 0, <1 empty item> ], length: 1, red: null }
truffle(development)>

:!: 「BN」は「BigNumber」という意味です。後々、テスト等で値の比較をするときに int とは扱いが変わってきますので、覚えておいてください。

Counter コントラクトの inc を呼び出す。

truffle(development)> counter.inc()
{
  tx: '0x10e87f126ce0934123624a8bd0d9c70a7d2d492fd1ade783646e828ccad6ad42',
  receipt: {
    transactionHash: '0x10e87f126ce0934123624a8bd0d9c70a7d2d492fd1ade783646e828ccad6ad42',
    transactionIndex: 0,
    blockHash: '0xc95665ead9fae2045cff12ba600d778a99454fbcdfcafe257f6c5dee61f76917',
    blockNumber: 5,
    from: '0x12c1b9b5152b14ce5af7a83947971108dc89e54d',
    to: '0x852560406282c788aef29abd5a9313588d86c3db',
    gasUsed: 42229,
    cumulativeGasUsed: 42229,
    contractAddress: null,
    logs: [],
    status: true,
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    rawLogs: []
  },
  logs: []
}
truffle(development)>

もう一度、Counter コントラクトの get を呼び出す。(値が0から1になっている)

truffle(development)> counter.get()
BN { negative: 0, words: [ 1, <1 empty item> ], length: 1, red: null }
truffle(development)>

テスト

生成

truffle(development)> truffle create test SimpleCounterTest
truffle(development)>

上記のコマンドで test ディレクトリの中に simple_counter_test.js ファイルが生成されます。

実装

エディタで simple_counter_test.js を開き、以下のように編集してください。

const Counter = artifacts.require("Counter");
 
/*
 * uncomment accounts to access the test accounts made available by the
 * Ethereum client
 * See docs: https://www.trufflesuite.com/docs/truffle/testing/writing-tests-in-javascript
 */
contract("SimpleCounterTest", function (/* accounts */) {
  it("should assert true", async function () {
    await Counter.deployed();
    return assert.isTrue(true);
  });
 
  it("Counter inc", async function () {
    let counter = await Counter.deployed();
 
    let before = await counter.get();
    await counter.inc();
    let after = await counter.get();
 
    // before and after is BN(BigNumber)
    return assert.equal(after.toNumber(), before.toNumber() + 1);
  });
 
  it("Counter dec", async function () {
    let counter = await Counter.deployed();
 
    let before = await counter.get();
    await counter.dec();
    let after = await counter.get();
 
    // before and after is BN(BigNumber)
    return assert.equal(after.toNumber(), before.toNumber() - 1);
  });
});

実行

truffle(development)> truffle test
Using network 'development'.
 
 
Compiling your contracts...
===========================
> Compiling .\contracts\Counter.sol
> Compiling .\contracts\Migrations.sol
> Artifacts written to C:\Users\shinobu\AppData\Local\Temp\test--15628-Lt5184Kc9NTX
> Compiled successfully using:
   - solc: 0.8.13+commit.abaa5c0e.Emscripten.clang
 
 
  Contract: SimpleCounterTest
    √ should assert true (55ms)
    √ Counter inc (1361ms)
    √ Counter dec (1419ms)
 
 
  3 passing (3s)
 
truffle(development)>