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