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.

Rick Park
  • 3,194
  • 2
  • 8
  • 25
PragMan
  • 173
  • 1
  • 1
  • 6

3 Answers3

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

goodvibration
  • 26,003
  • 5
  • 46
  • 86
  • 2
    Thanks a lot @goodvibration! I'll try your suggestions and give the feedback! – PragMan Feb 14 '19 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 Feb 22 '19 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). – goodvibration Feb 25 '20 at 08:15
  • I tried toString() n BN and got the same error Error: Number can only safely store up to 53 bits – shivamDev31 May 23 '20 at 02: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>.

Aaron Digulla
  • 428
  • 4
  • 10
  • no, you shouldn't use .to.eql otherwise you'll get something like AssertionError: expected '1000000000000000000' to deeply equal <BN: de0b6b3a7640000> – lebed2045 May 27 '21 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'));
rgeorgy
  • 41
  • 2
  • 1
    With truffle v5 we have bn.js instead of bignumber.js, hence I solved the problem with chai-bn. Thank you – PragMan Feb 22 '19 at 13:34