Ethereum
solidity
truffle

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


はじめに

これは Ethereum Advent Calendar 2018 の3日目です。

2日目は Morinikiz さんです。


自己紹介


この記事について

Solidity ではメソッドの overload が可能なのですが、truffle で素直にテストを書いたらエラーになった、という事がありました。

今回はその際の回避方法について説明していきます。


準備


環境

$ truffle version

Truffle v4.1.14 (core: 4.1.14)
Solidity v0.4.24 (solc-js)


Solidity

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

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);
}
}


テストコード

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

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 を使うか引数指定によって切り替える事ができるので、それを利用できます。

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 系を使うように書く必要があります。

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 さんです。