Day 9: 両方のいいとこ取り: SolidityとJavascriptにおけるスマートコントラクト ユニットテスト

2018/08/15

スマートコントラクトは、分散型アプリやカスタム暗号通貨を作成するための強力なツールです。従来のソフトウェアとは異なり、一度デプロイされると、元に戻したり、パッチを適用したりするのが難しくなります。そして 暗号通貨市場の時価総額が2000億ドルを超えているため、リスクが大きいです!ユニットテストは、私たちのスマートコントラクトが期待通りに動作するかを確認するのに役立ちます。

では、どのようにユニットテストを書けばよいのでしょうか?SolidityとJavaScriptの両方でテストを書くことが可能ですが、2つは完全には互換性がありません。簡単なトークンのテストの例を見てみましょう。  (簡単というのは、必ずしも容易ではなく、複雑さが少ないことを意味します!) もし一緒に進めたい場合は、私の作業はgithub repoに置いてあります。最初にtruffleganache-cliをインストールする必要があります。私はTruffle v4.1.11、Solidity v0.4.24、Ganache v6.1.6を使用しており、Mac OS X High Sierra 10.13.6で実行しています。

私の初期トークンは、次のようなコンストラクタを持っていました:

pragma solidity 0.4.24;contract Mikancoin {  uint public totalSupply;  mapping (address => uint) public balanceOf;  constructor(uint _initialSupply) public {    totalSupply = _initialSupply;    balanceOf[msg.sender] = _initialSupply;    emit Transfer(0x0, msg.sender, totalSupply);  }...

そして、migrations/2_deploy_contracts.jsは次のようになります:

var Mikancoin = artifacts.require("Mikancoin");module.exports = function(deployer) {    deployer.deploy(Mikancoin, 100);};

さあ、ganacheコマンドラインインターフェースを立ち上げましょう:

ganache-cli

別のターミナルで、これをテストブロックチェーンにデプロイしましょう:

truffle compile && truffle migrate

素晴らしい、test/TestMikancoin.solでSolidityで最初のユニットテストを書きましょう:

pragma solidity 0.4.24;

import 'truffle/Assert.sol';
import 'truffle/DeployedAddresses.sol';
import '../contracts/Mikancoin.sol';

contract TestMikancoin {
  function testConstructor() public {
    // Get the address and cast it
    Mikancoin mikan = Mikancoin(DeployedAddresses.Mikancoin());
    Assert.equal(mikan.totalSupply(), 100, "Total supply");
    Assert.equal(mikan.balanceOf(this), 0, "We have no Mikan");
  }
}

私たちは、DeployedAddressesを使用して、Mikancoinがどこにデプロイされたかを調べ、そのアドレスをMikancoinとしてキャストできます。次に、アサートを使用して、コインが正しく動作しているかを確認できます。truffle testを実行すると、私たちが100Mikanを持つコインを正常にデプロイしたことが示され、私たちの残高はゼロです。

ここで最初の問題に直面します。トークンが送信されないと、テストを書くのは非常に面白くもなく、徹底的でもありません。  最初のミカンを受け取ったのは誰でしょう?移行スクリプトが初期のデプロイを行ったので、おそらく果物を手にしたのは内部のトラッフルアドレスです。それを確認するために、トラッフルコンソールツールを使用してみましょう:

Truffle Console

Mikancoinコンストラクタを呼び出した人や、消費されたガスの量を確認できます。また、これはganache-cliによって作成された最初のethアドレスと一致することもわかります。私たちはganache-cliを設定して常に同じ10のアドレスを生成させることもできますが、それでもsolidityでは簡単に制御することはできません。コントラクトを少し修正して、デプロイ時に誰がコインを受け取るかを決定できるようにしましょう。

 constructor(uint _initialSupply, address _initialOwner) public {
    require(_initialOwner != 0x0);
    totalSupply = _initialSupply;
    balanceOf[_initialOwner] = _initialSupply;
    emit Transfer(0x0, _initialOwner, totalSupply);
  }

私たちはまた、Mikancoinを簡単に管理するためのヘルパーコントラクトを書けます。

pragma solidity 0.4.24;

import './Mikancoin.sol';

contract MikanFarm {

  Mikancoin [] public deployed;

  function deployMikancoin(uint _initialSupply) public returns (Mikancoin){
    Mikancoin latest = new Mikancoin(_initialSupply, msg.sender);
    deployed.push(latest);
    return latest;
  }
}

次に、移行スクリプトを更新します:

var MikanFarm = artifacts.require("MikanFarm");

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

そして最終的には、私たちがMikanをコントロールするテスト!

pragma solidity 0.4.24;

import 'truffle/Assert.sol';
import 'truffle/DeployedAddresses.sol';
import '../contracts/MikanFarm.sol';
import '../contracts/Mikancoin.sol';

contract TestMikancoin {

  function testTransfer() public {
    uint startTokens = 100;
    MikanFarm farm = MikanFarm(DeployedAddresses.MikanFarm());
    Mikancoin mikan = farm.deployMikancoin(startTokens);
	Assert.equal(mikan.balanceOf(this), startTokens, "We should have 100 Mikan");
	address fox = 0x284A84baA00626e2773a1138D53923b4acAED2F4;
	Assert.equal(mikan.balanceOf(fox), 0, "Fox has 0 mikan");

	uint tokens	= 7;
	Assert.isTrue(mikan.transfer(fox, tokens), "Transfer should succeed");
	Assert.equal(mikan.balanceOf(fox), tokens, "Fox balance after transfer");
	Assert.equal(mikan.balanceOf(this), startTokens - tokens, "Sender balance after transfer");
  }
}

今、私たちは転送のrequireステートメントをテストしたいと考えています。そして、ゼロアドレスにトークンを送信するような悪いことはできないことを確認します。solidityではこれが難しいです。なぜなら、try-catchがないからです。0x0に送信すると、本当にひどいスタックトレースが発生します:

function testBadTransfer() public {
    MikanFarm farm = MikanFarm(DeployedAddresses.MikanFarm());
    Mikancoin mikan = farm.deployMikancoin(100);
    Assert.isFalse(mikan.transfer(0x0, 5), "Transfer to 0 address should fail"); // require() fails and therefore this will revert
  }

このテストを追加し、truffle testを実行すると、次のようになります:

Truffle Throw

以前はスローをテストする方法がありましたが、require&revertに切り替えてからは、これらの技術はもはや機能しません。このテストの失敗ケースをデモするために、JavaScriptに切り替えましょう。注意: このテストを実行する前に、ルートディレクトリでnpm installを実行する必要があります。chai依存関係を取得するために。

var Mikancoin = artifacts.require("Mikancoin.sol");

const should = require('chai').should();

contract('Mikancoin', function(accounts) {

  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';

  beforeEach(async function () {
    this.mikancoin = await Mikancoin.new(300, accounts[0]);
  });

  describe("transfer", function() {
    describe("when the recipient is the zero address", function() {
      const amount = 123;
      it("reverts", async function () {
        try {
          await this.mikancoin.transfer(ZERO_ADDRESS, amount, { from: accounts[0] });
        } catch (error) {
          error.message.should.include('revert', `Expected "revert", got ${error} instead`);
          return;
        }
        should.fail('Revert did not happen');
      });
    });
  });
});

ソリディティでのテスト後、JavaScriptは簡単なケーキのように感じます。さて、すべてをtruffle testで実行しましょう。TruffleはJavaScriptとSolidityの両方のテストファイルを取得し、両方の良さを提供します:

Truffle Tests Passing

続きがあります...