0

I am writing truffle tests for my Ethereum contract, and I am having difficulties understanding why the numbers are not matching. Any help from this community will be highly appreciated.

Concept of the Voting smart contract

With an instance of a Poll.sol contract, users can vote on certain options (once per address). However, users can also put their votes for sale, so that another address can buy that vote and therefore, vote more than once. When buying a vote, if the buyer sends more ETH to the contract than needed, I want to give change back.

For the sake of this question, the relevant method is buysVote:


struct Vote {
    bool used;
    bool forSale;
    uint price;
    address payable owner;
}
mapping(address => Vote) private votes; // stores the votes of everyone

...

     /**
     * Puts a vote for sale for a specified price.
     * @param _voteAddr The vote to be put for sale. Msg.owner must own this vote.
     * 
     */
    function sellVote(address _voteAddr, uint price) public notVoted(_voteAddr) ownsVote(_voteAddr) {
        require(price > 0, "Price must be higher than 0");

        Vote storage _vote = votes[_voteAddr];
        _vote.price = price;
        _vote.forSale = true;

        emit VoteForSale(_voteAddr, price);
    }


    /**
     * Buys a vote for sale. Enough funds must be transferred to this method. 
     * More funds than necessary will be refunded.
     * @dev This method only works if the vote has not been used AND is for sale 
     * @param _voteAddr the address of the seller whose vote is intended to buy
     */
    function buysVote(address payable _voteAddr) public payable notVoted(_voteAddr) forSale(_voteAddr) {
        Vote storage _vote = votes[_voteAddr];

        require(msg.value >= _vote.price, "Not enough funds transferred");
        
        uint change = msg.value - _vote.price;

        _voteAddr.transfer(_vote.price); // sends the price value to the seller

        if (change > 0) {
            msg.sender.transfer(change); // send the change back to the buyer
        }

        _vote.forSale = false;
        _vote.owner = msg.sender;
        emit VoteOwnershipChanged(_voteAddr, msg.sender);
    }

Now my Truffle test case looks like this:


it ("Should give change when buying vote", async () => {
            let ask = web3.utils.toWei("0.50");
            let bid = web3.utils.toWei("1");
            
            let poll = await createPoll();

            let voteBuyer_initialBalance = await web3.eth.getBalance(voteBuyer);
            let voteSeller_initialBalance = await web3.eth.getBalance(voteSeller);

            await poll.sellVote(voteSeller, ask, {from: voteSeller}); // voteSeller puts vote for sale
            let result = await poll.buysVote(voteSeller, {from: voteBuyer, value: bid}); // voteBuyer buys vote from voteSeller with 1000 more than needed

            let gasUsed = result.receipt.gasUsed; // how much gas was used by the operation
            let tx = await web3.eth.getTransaction(result.tx); // get transaction to obtain gas price
            let gasPrice = parseInt(tx.gasPrice); // get the gas price

            let gasCost = gasPrice * gasUsed; // final gas cost is gas used multiplied by gas cost
            let totalPaid = parseInt(ask) + gasCost;

            let voteBuyer_finalBalance = await web3.eth.getBalance(voteBuyer); 
            let voteSeller_finalBalance = await web3.eth.getBalance(voteSeller); 

            let voteSeller_difference = parseInt(voteSeller_initialBalance) - parseInt(voteSeller_finalBalance);
            let voteBuyer_difference = parseInt(voteBuyer_initialBalance) - parseInt(voteBuyer_finalBalance);

            console.log("gasPrice", gasPrice);
            console.log("gasUsed", gasUsed);
            console.log("gasCost", gasCost);

            console.log("ask", ask);

            console.log("voteSeller initial balance", voteSeller_initialBalance);
            console.log("voteSeller final balance", voteSeller_finalBalance);
            console.log("voteBuyer initial balance", voteBuyer_initialBalance);
            console.log("voteBuyer final balance", voteBuyer_finalBalance);
            
            console.log("voteSeller final wallet difference:", voteSeller_difference);
            
            // EXPECTED VALUES TO MATCH: voteBuyer wallet is decreased by price paid + gas fees
            console.log("voteBuyer final wallet difference", voteBuyer_difference);
            console.log("Final cost:", totalPaid); 

            assert.equal(voteBuyer_difference, totalPaid, "Change is wrong"); // account should be only 0.50 + the gas fee less in value
        });

Output from the console:

gasPrice 20000000000
gasUsed 55353
gasCost 1107060000000000
voteSeller initial balance 112234024100000505000
voteSeller final balance 112732679880000505000
voteBuyer initial balance 87230841719999515000
voteBuyer final balance 86729734659999515000
voteSeller final wallet difference: -498655780000006140
voteBuyer final wallet difference 501107059999997950
Final cost: 501107060000000000


      Change is wrong
      + expected - actual

      -501107059999997950
      +501107060000000000

So, I am expecting that the buyer will have his wallet decrease by exactly the ask price (500000000000000000) + gas fees total cost (1107060000000000) which is a total of 501107060000000000.

However, the buyer account final difference is that of 501107059999997950.

So there is a difference of 2050 wei between the expected value and the actual difference.

QUESTION Why / where is this number coming from?

3
  • 1
    The script uses parseInt so it will use javascrip's numbers that have limited precision. I can't test right now but I'm willing to bet that should explain the missing wei. If any quantity is greater than Number.MAX_SAFE_INTEGER it will use double and lose precision.
    – Ismael
    Jul 1 at 4:16
  • 1
    See this question ethereum.stackexchange.com/questions/90403/….
    – Ismael
    Jul 1 at 4:17
  • Thanks! Using web3.utils.toBN() instead of parseInt AND using the bn.sum, .sub, .mul methods did the trick. Jul 1 at 9:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.