17

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.

3 Answers 3

15

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).

4
  • 2
    Thanks a lot @goodvibration! I'll try your suggestions and give the feedback!
    – PragMan
    Commented Feb 14, 2019 at 20:03
  • Feedback: problem solved using chai-bn with bn.js! I was using toNumber(), hence there was the intrinsic limit of 53 bits. Thank you @goodvibration !
    – PragMan
    Commented Feb 22, 2019 at 13:33
  • 3
    Note to future readers: use toFixed() for BigNumber (Truffle 4.x / web3 0.x) and toString() for BN (Truffle 5.x / web3 1.x). Commented Feb 25, 2020 at 8:15
  • I tried toString() n BN and got the same error Error: Number can only safely store up to 53 bits Commented May 23, 2020 at 2:45
7

To do this in pure chai, you can use these two approaches:

const expected = web3.utils.toBN('123.052');
const actual = await meta.getBalance.call(account_two);
expect(actual).to.eql(expected); // compare to BigNumber

or use strings:

const actual = (await meta.getBalance.call(account_two)).toString();
expect(actual).to.equal('123.052');

Note that you have to use eql() (deep equality) instead of equal() when comparing BigNumbers or you'll get confusing errors like BN<1> is not equal to BN<1>.

1
  • no, you shouldn't use .to.eql otherwise you'll get something like ` AssertionError: expected '1000000000000000000' to deeply equal <BN: de0b6b3a7640000> `
    – lebed2045
    Commented May 27, 2021 at 11:08
3

As goodvibration mentioned, please avoid casting BigNumbers to js numbers. You can assert BigNumnbers using chai-bignumnber, as follows.

(await contract.functionRetuningInt()).should.be.bignumber.equal(mixed_value);

Even better, openzeppeling-test-helpers can make your testing life a lot easier, specially for checking balance difference. Here's an example for their docs:

(await balance.difference(receiver, () =>
  send.ether(sender, receiver, ether('1')))
).should.be.bignumber.equal(ether('1'));
1
  • 1
    With truffle v5 we have bn.js instead of bignumber.js, hence I solved the problem with chai-bn. Thank you
    – PragMan
    Commented Feb 22, 2019 at 13:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.