[Ethereum] How to use bignumbers in truffle tests

bignumberjavascriptsoliditytruffle

I am testing a smartcontract with Truffle V5. All works well if I do not use all decimals. If I set 18 decimals all explodes. The test code is this (from truffle docs):

const MetaCoin = artifacts.require("MetaCoin");

contract("2nd MetaCoin test", async accounts => {
  it("should put 10000 MetaCoin in the first account", async () => {
    let instance = await MetaCoin.deployed();
    let balance = await instance.getBalance.call(accounts[0]);
    assert.equal(balance.valueOf(), 10000);
  });

  it("should call a function that depends on a linked library", async () => {
    let meta = await MetaCoin.deployed();
    let outCoinBalance = await meta.getBalance.call(accounts[0]);
    let metaCoinBalance = outCoinBalance.toNumber();
    let outCoinBalanceEth = await meta.getBalanceInEth.call(accounts[0]);
    let metaCoinEthBalance = outCoinBalanceEth.toNumber();
    assert.equal(metaCoinEthBalance, 2 * metaCoinBalance);
  });

  it("should send coin correctly", async () => {
    // Get initial balances of first and second account.
    let account_one = accounts[0];
    let account_two = accounts[1];

    let amount = 10;

    let instance = await MetaCoin.deployed();
    let meta = instance;

    let balance = await meta.getBalance.call(account_one);
    let account_one_starting_balance = balance.toNumber();

    balance = await meta.getBalance.call(account_two);
    let account_two_starting_balance = balance.toNumber();
    await meta.sendCoin(account_two, amount, { from: account_one });

    balance = await meta.getBalance.call(account_one);
    let account_one_ending_balance = balance.toNumber();

    balance = await meta.getBalance.call(account_two);
    let account_two_ending_balance = balance.toNumber();

    assert.equal(
      account_one_ending_balance,
      account_one_starting_balance - amount,
      "Amount wasn't correctly taken from the sender"
    );
    assert.equal(
      account_two_ending_balance,
      account_two_starting_balance + amount,
      "Amount wasn't correctly sent to the receiver"
    );
  });
});

This is the smart contract I need to test (from MetaCoin.sol file, modified on order to use latest compiler (currently I use 0.5.2) and etc.). The original can be found here:

pragma solidity >=0.4.25 <0.6.0;

import "./ConvertLib.sol";

// This is just a simple example of a coin-like contract.
// It is not standards compatible and cannot be expected to talk to other
// coin/token contracts. If you want to create a standards-compliant
// token, see: https://github.com/ConsenSys/Tokens. Cheers!

contract MetaCoin {

// I add this section in EIP20 style
    string public symbol;
    string public  name;
    uint8 public decimals;
    uint _totalSupply

    mapping (address => uint) balances;

    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    constructor() public {

// I modified constructor to introduce symbol, name, decimals and totalSupply
        symbol = "MTC";
        name = "MetaCoin Example Token";
        decimals = 18;
        _totalSupply = 10000 * 10**uint(decimals);  
        balances[tx.origin] = _totalSupply;
    }

    function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
        if (balances[msg.sender] < amount) return false;
        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        emit Transfer(msg.sender, receiver, amount);
        return true;
    }

    function getBalanceInEth(address addr) public view returns(uint){
        return ConvertLib.convert(getBalance(addr),2);
    }

    function getBalance(address addr) public view returns(uint) {
        return balances[addr];
    }
}

All is ok if decimals is, for instace "1" or "2", but when I use "18" i loose the control. How can I implement bignumbers treatment in the javascript tests? I tried, but it does not work.

Best Answer

Due to the limitation imposed by Number.MAX_SAFE_INTEGER, try to stick to the following rules:

Rule #1:

Avoid using toNumber() on any value returned from a contract function whose return-value type is one of the following:

  • uint64
  • uint128
  • uint256

This also applies for public variables (since the compiler generates implicit getter functions for them).

Rule #2:

Keep the returned value in its original type (BigNumber on Truffle 4.x / web3 0.x, and BN on Truffle 5.x / web3 1.x), and use only the functions of this type (e.g., plus, minus, mul, div, equals, etc).

Rule #3:

For printout, you're best off using toFixed(), in order to avoid precision-loss as a result of scientific notation (e.g., 123.456e78).

Please note that rule #1 also applies for on-chain functions which return an address value (which is converted to string at the off-chain side).

Related Topic