Problem
When you creating Smart Contracts using Truffle Framework, you able to create a beautiful and clean unit tests for Smart Contracts in javascript. Truffle provides an abstraction on web3
library for every smart contract. For example, calling for method transfer
in ERC20 Smart Contract will look like:
ERC20 Smart Contract transfer method
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
// SafeMath.sub will throw if there is not enough balance.
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
Transfer(msg.sender, _to, _value);
return true;
}
Calling transfer
method in Truffle javascript test
const ERC20Token = artifacts.require('ERC20Token.sol');
//Some code here
const tokenInstance = await ERC20Token.new();
//Some code here
//Call the transfer method in test
await tokenInstance.transfer('0x24bB2BE1fAe8404fb0389f4e34E87C6852E5b33a', 1000);
As you could see we could use a handy abstraction for calling methods of the Smart Contract based on truffle-contract module. In another case for calling of the smart contract method we should prepare and send the transaction via web3 library.
So the truffle-contract is very handy, but unfortunately, in the current state it's could not handle overloaded functions. I've faced this problem while working on the ERC233 token for Biometrids ICO. If you have two methods with similar signatures, but different arguments - truffle-contract
will handle only the first implementation of this method. In other words - there is no way to call all implementations of overloaded function via truffle contract abstraction. For example:
ERC233 Smart Contract overloaded transfer method
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
// SafeMath.sub will throw if there is not enough balance.
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
Transfer(msg.sender, _to, _value);
return true;
}
function transfer(address _to, uint256 _value, bytes _data) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
tryContractTokenFallback(_to, msg.sender, _value, _data);
Transfer(msg.sender, _to, _value);
return true;
}
Trying to call overloaded transfer
method in Truffle javascript test
const ERC233Token = artifacts.require('ERC233Token.sol');
//Some code here
const tokenInstance = await ERC233Token.new();
//Some code here
//Call the transfer method in test
await tokenInstance.transfer('0x24bB2BE1fAe8404fb0389f4e34E87C6852E5b33a', 1000);
//This call with 3rd `bytes` type argument added will throw an error
await tokenInstance.transfer('0x24bB2BE1fAe8404fb0389f4e34E87C6852E5b33a', 1000, '0x00');
My solution
For testing overloaded version of ERC233 we could drop the abstraction and create send the transaction to the deployed contract directly via web3
library. To do this we need a encode method call with passed arguments.
To encode a method call we need method ABI
which could be found in the build/contracts/YoursAmazingContract.json
after the contract was built by the truffle build
command. Well, we also need arguments that will be passed in the encodeFunctionCall
method.
encodeFunctionCall
method is placed in web3-eth-abi
module, so we need to install and include it as well. Let's start.
1. Install and require web3-eth-abi
module
$ npm install web3-eth-abi
const web3Abi = require('web3-eth-abi');
2. Get an instance of web3
library
const ERC233Token = artifacts.require('ERC233Token.sol');
const tokenInstance = await ERC233Token.new();
const web3 = ERC233Token.web3;
3. Get method ABI from contract JSON and put it in the constant
const overloadedTransferAbi = {
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
},
{
"name": "_data",
"type": "bytes"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
};
4. Encode function call with passed arguments
const transferMethodTransactionData = web3Abi.encodeFunctionCall(
overloadedTransferAbi,
[
'0x24bB2BE1fAe8404fb0389f4e34E87C6852E5b33a',
1000,
'0x00'
]
);
5. Call overloaded method
await web3.eth.sendTransaction({from: owner, to: tokenInstance.address, data: transferMethodTransactionData, value: 0});
Putting all together
Let's create a simple example of the unit test based on steps mentioned above.
const web3Abi = require('web3-eth-abi');
const tokenInstance = await ERC233Token.new();
const web3 = ERC233Token.web3;
const overloadedTransferAbi = {
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
},
{
"name": "_data",
"type": "bytes"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
};
contract('ERC233Token', function (accounts) {
it('Check that via ERC223 overloaded method', async function () {
const transferMethodTransactionData = web3Abi.encodeFunctionCall(
overloadedTransferAbi,
[
'0x24bB2BE1fAe8404fb0389f4e34E87C6852E5b33a',
1000,
'0x00'
]
);
await web3.eth.sendTransaction({from: owner, to: tokenInstance.address, data: transferMethodTransactionData, value: 0});
assert.equal(
(await instance.balanceOf('0x24bB2BE1fAe8404fb0389f4e34E87C6852E5b33a')).toString(),
'1000'
);
});
});
Software versions used in this article
- Truffle:
v4.0.1
- Solidity:
v0.4.18
- web3-eth-abi:
v1.0.0-beta.24