Solidity で overload したメソッドを呼び出す

# はじめに

これは [Ethereum Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ethereum) の3日目です。
2日目は [Morinikiz](https://qiita.com/Morinikiz) さんです。

# 自己紹介

* HN : ogwmtnr
* アプリケーションエンジニア
* 普段は[株式会社ドリコム](https://www.drecom.co.jp/) の [DRIP](http://drip.drecom.co.jp) というところで働いています
* 豚バラ肉および豚バラ肉を用いた料理が好きです
* 自社ブログ記事
  * [スマートコントラクトの Method ID 衝突はどのように回避されるのか](https://tech.drecom.co.jp/test-smart-contract-duplicated-method-ids/)
  * [DApps のユーザー認証に web3.eth.personal.sign を使おう！](https://tech.drecom.co.jp/dapps-use-web3-eth-personal-sign/)
  * [DApps の壁を超えて利用可能なトークンを自由に生成できるスマートコントラクト |||||| (6 pillars) を公開しました](https://tech.drecom.co.jp/smart-contract-llllll/)
  * [Truffle + Ganache + Metamask を使って Ðapps の世界に旅立とう](https://tech.drecom.co.jp/go_to_the_dapps_world/)

# この記事について

Solidity ではメソッドの overload が可能なのですが、truffle で素直にテストを書いたらエラーになった、という事がありました。
今回はその際の回避方法について説明していきます。

# 準備

## 環境

```
$ truffle version
Truffle v4.1.14 (core: 4.1.14)
Solidity v0.4.24 (solc-js)
```

## Solidity

overload しているメソッドを含む、このようなコントラクトを準備します。

``` solidity
pragma solidity ^0.4.22;

contract OverloadContract {

  mapping(address => uint256) internal voteMap;

  event Vote(
    address indexed _to,
    uint256 _count
  );

  event Clear(
    address indexed _to,
    uint256 _clearCount
  );

  function vote(address _to) external {
    voteMap[_to]++;
    emit Vote(_to, 1);
  }

  function vote(address _to, uint256 _count) external {
    voteMap[_to] += _count;
    emit Vote(_to, _count);
  }

  function clear() external {
    uint256 count = voteMap[msg.sender];
    voteMap[msg.sender] = 0;
    emit Clear(msg.sender, count);
  }
}
```

## テストコード

全てのメソッドを走らせるだけのシンプルなテストを用意します。

``` js
const OverloadContract = artifacts.require("OverloadContract")

contract('OverloadContract', (accounts) => {
  let obj
  const voteTo = accounts[1]

  before(async () => {
    obj = await OverloadContract.new()
  })

  it('vote', async () => {
    await obj.vote(voteTo)
  })

  it('vote with count', async () => {
    await obj.vote(voteTo, 10)
  })

  it('clear', async () => {
    await obj.clear({from: voteTo})
  })
})
```

# テストする

```
$ truffle test test/OverloadContract.test.js
Using network 'development'.



  Contract: OverloadContract
    1) vote
    > No events were emitted
    ✓ vote with count (151ms)
    ✓ clear (55ms)


  2 passing (379ms)
  1 failing

  1) Contract: OverloadContract
       vote:
     Error: Invalid number of arguments to Solidity function
      at Object.InvalidNumberOfSolidityArgs (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/errors.js:25:1)
      at SolidityFunction.validateArgs (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/function.js:74:1)
      at SolidityFunction.toPayload (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/function.js:90:1)
      at SolidityFunction.sendTransaction (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/function.js:163:1)
      at SolidityFunction.execute (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/function.js:256:1)
      at /usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-contract/contract.js:204:1
      at new Promise (<anonymous>)
      at /usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-contract/contract.js:155:1
      at process._tickCallback (internal/process/next_tick.js:68:7)
```

`Error: Invalid number of arguments to Solidity function` すなわちメソッドの引数が異なるよ！というエラーが返ってきます。
見ると `vote(voteTo)` が失敗して `vote(voteTo, 10)` が成功している。
つまり `contract.vote` は引数2つの方が採用されていて、`vote(voteTo)` だと引数が足らずにエラーとなっているようです。
こうなると overload したメソッドのテストが書けないので、困ってしまいます。

# エラーを回避

## `[]` で引数を指定する

どちらの `vote` を使うか引数指定によって切り替える事ができるので、それを利用できます。

``` js
const OverloadContract = artifacts.require("OverloadContract")

contract('OverloadContract', (accounts) => {
  let obj
  const owner = accounts[0]
  const voteTo = accounts[1]

  before(async () => {
    obj = await OverloadContract.new({from: owner})
  })

  it('vote', async () => {
    await obj.contract.vote['address'](voteTo, {from: owner})
  })

  it('vote with count', async () => {
    await obj.contract.vote['address,uint256'](voteTo, 10, {from: owner})
  })

  it('clear', async () => {
    await obj.clear({from: voteTo})
  })
})
```

## web3 1.0 系を使う

試してみたところ web3 1.0 系 であれば引数の数によって呼び出したい Solidity function を切り替えてくれるようで、こちらも利用できそうです。
truffle test で利用できる web3 は 0.20 系なので、1.0 系を使うように書く必要があります。

``` js
const OverloadContract = artifacts.require("OverloadContract")
const Web3 = require('web3')

contract('OverloadContract', (accounts) => {
  let contract
  const owner = accounts[0]
  const voteTo = accounts[1]

  before(async () => {
    const web3Newer = new Web3(web3.currentProvider)
    const obj = await OverloadContract.new({from: owner})
    contract = new web3Newer.eth.Contract(obj.abi, obj.address)
  })

  it('vote', async () => {
    await contract.methods.vote(voteTo).send({from: owner})
  })

  it('vote with count', async () => {
    await contract.methods.vote(voteTo, 10).send({from: owner})
  })

  it('clear', async () => {
    await contract.methods.clear().send({from: voteTo})
  })
})
```

去年[こちら]()にあるような事例が報告されていたのですが、どこかで解消された模様？

# おわりに

これで安心して、自作したメソッドを網羅するスマートコントラクトのテストコードが書けそうです。
truffle の進化によってこのあたり解消する可能性もあるので、その際は truffle 標準なやり方にシフトするのが良いかと思います。

# 明日は

[Hirata Takahiro](https://qiita.com/takahiro096041) さんです。