Search code examples
javascriptethereumsolidityweb3jstruffle

Web3js invocation and truffle invocation behaving differently


I have a contract which looks like this -

contract Calculator {
    uint public result;

    constructor() public {
       result = 777;   //note the initial value
    }

    function add(uint a, uint b) public returns (uint, address) {
       result = a + b;
       return (result, msg.sender);
    }
}

When I invoke the function on truffle I get a proper transaction -

truffle(development)> await calculator.add(5,11)
{
  tx: '0xa66e94bb28810bb2a861c97ee149718afa599d47b7b1c6e55743ea657fdeef56',
  receipt: {
    transactionHash: '0xa66e94bb28810bb2a861c97ee149718afa599d47b7b1c6e55743ea657fdeef56',
    transactionIndex: 0,
    blockHash: '0x6ae4e3ce65f1e177c419306a50662ed46f40c729a6a18ede028b07e63dd12f61',
    blockNumber: 6,
    from: '0x5d88950b52f89ad66906fc263e8c35ddacff04d4',
    to: '0x7c8beb382c70cbf12b41fd4e5d74cfee53fdc391',
    gasUsed: 26074,
    cumulativeGasUsed: 26074,
    contractAddress: null,
    logs: [ [Object] ],
    status: true,
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000200000000000000000000000000000000000000',
    rawLogs: [ [Object] ]
  },
  logs: [
    {
      logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0xa66e94bb28810bb2a861c97ee149718afa599d47b7b1c6e55743ea657fdeef56',
      blockHash: '0x6ae4e3ce65f1e177c419306a50662ed46f40c729a6a18ede028b07e63dd12f61',
      blockNumber: 6,
      address: '0x7C8beb382C70CbF12b41fd4e5d74CfEe53FDc391',
      type: 'mined',
      id: 'log_28a5e84f',
      event: 'Added',
      args: [Result]
    }
  ]
}

And I am invoking the same function using Web3js like this -

const Web3 = require("web3");
const CalculatorContract = require("../build/contracts/Calculator.json");

let web3 = new Web3(new Web3.providers.WebsocketProvider("ws://127.0.0.1:7545"));

async sendTransaction () {
    let accounts = await web3.eth.getAccounts();

    let contractAddress = "0x7C8beb382C70CbF12b41fd4e5d74CfEe53FDc391";
    let calculatorContract = new web3.eth.Contract(CalculatorContract.abi, contractAddress);

    console.log("Calculator adding: ", await calculatorContract.methods.add(11, 88).call({
        from: '0x38e3614A5Cf95f0DBb858D9E3752Ac477DA70ccD'
    }));
    console.log("Calculator result: ", await calculatorContract.methods.result().call());
}

When I invoke this above function I get output like this -

Calculator adding:  Result {0: '99', 1: '0x5d88950b52F89AD66906fC263E8C35DdacFf04D4'}
Calculator result:  777     //why 777? the last invocation should rewrite 777 to 99

Now my questions are -

  1. Why truffle invocation is creating transaction, but Web3js invocation is not creating any transaction? How can I make these both behave same?
  2. When invoking with truffle (I mean when transaction is returned), how can I get the return value of the method invocation?

Software versions:

truffle(development)> truffle version
Truffle v5.1.51 (core: 5.1.51)
Solidity v0.5.16 (solc-js)
Node v15.1.0
Web3.js v1.2.9

Ganache: 2.5.4
Nodejs: v15.1.0
Web3js: ^1.3.0


Solution

  • Web3.js contract objects are different from Truffle contract objects. There are slight differences in the structure and of course, because it is all untyped JavaScript, you have no idea what inputs or outputs functions are going to have.

    Historically, Ethereum transactions did not have return values. Web3.js behavior predates this time, and might not support returning the transaction value directly. However EIP 1288 document how the return value is available through getTransactionReceipt() and receipt object.

    To have Web3.js to modify EVM state you need to do send() instead of call(). This is yet another weakly typed issue, as one should not confuse view and write functions.

    Try:

        console.log("Calculator adding: ", await calculatorContract.methods.add(11, 88).send({
            from: '0x38e3614A5Cf95f0DBb858D9E3752Ac477DA70ccD'
        }));
    

    To address the disparicities between Truffle And Web3.js. The easiest way to address behavioural differences is not using Truffle artifacts at all, but using Web3.js contracts everywhere.

    Ps. I recommend OpenZeppelin SDK (Node.js / TypeScript) over Truffle. It is somewhat more sane with TypeScript typing and can be also used as a Web3.js replacement in frontend and backend code. Or even better with Python and Web3.py, as these leave less room for guessing games like in your question, if you can afford switching a language.