[Ethereum] Truffle test: getting Transaction Object instead of uint result

soliditytestingtruffle

I have a method in my contract like so:

  mapping(address => uint256) allowance;

  ...

  function setAllowance(address user, uint256 amount) external onlyOwner nonReentrant returns (uint256) {
    allowance[user] = amount;
    emit AllowanceLimitChange(user, amount, ChangeType.None, allowance[user]);
    return allowance[user];
  }

In my truffle test, I am expecting it to return me a uint256, but it actually returns me a transaction object! Below is my test code:

  it("should set allowance limits to non-zero", async () => {
    try {
      const value = web3.utils.toWei("0.2");
      const newAllowance = await contract.setAllowance(accounts[1], value);
      assert.equal(value, newAllowance); // assertionError
    } catch (err) {
      assert(false); // always come here because of assertionError due to type mismatch
    }
  });

Screenshot of Transaction Object received:
enter image description here

Best Answer

TLDR;

Similar to this issue here , use .call after the function name. Then it works out!

      const newLimit = await contract.setAllowance.call(kid1, value); // the .call() is the fix

Edit: (adding more details in the line of @GoodVibration's comments)


setAllowance is a non-constant call, which means it modifies contract state in some way. constant was used in older solidity code, which in modern versions are indicated via view (only reads state) or pure (no read from, or write to contract state).

When we invoke such a non-constant method, we will only get the transaction response as the return value, irrespective of what return value we might have coded in (in this case, uint256). Thus, we have two options:

  1. use .call : this sort of "evaluates" the code inside the method, but not cause a transaction to happen, and thus no state change will be persisted. A test case to help explain things better:
  it("will set allowance limits to non-zero within the scope of the method", async () => {
    try {
      const value = web3.utils.toWei("0.2");
      // https://ethereum.stackexchange.com/a/90342/22522
      const newLimit = await contract.setAllowance.call(accounts[1], value);
      assert.equal(value, newLimit); // we get a return value
    } catch (err) {
      console.log("error:\n", err);
      assert(false);
    }
  });

  it("proves that above call does not persist state change", async () => {
    const allowance = await contract.getAllowance(kid1);
    assert.equal(allowance, 0); // allowance did not change at all!!
  });
  1. Invoke the method, and use Events for testing: when we write await contract.method(... args) , that method is actually invoked, transaction goes through mining, and events are fired. While this is great, it will always return a Tx object as response, and not the return type you coded. As such you need to assert on the events that gets fired. something like this:
  // smart contract
  event AllowanceChange(address user, uint256 oldLimit, string delta, uint256 newLimit);
  
  ...

  function setAllowance(address user, uint256 amount) external onlyOwner nonReentrant returns (uint256) {
    allowance[user] = amount;
    emit AllowanceChange(user, amount, "set", allowance[user]);
    return allowance[user];
  }

      
  // Truffle JS Test
  const value = web3.utils.toWei("0.2");
  const result = await contract.setAllowance(accounts[1], value); 
    truffleAssert.eventEmitted(
    result,
    "AllowanceLimitChange",
    (ev) => ev.newLimit.toString() === value,
    "Allowance Change event should be triggered"
  );
Related Topic