42

In a Buidler/Hardhat test I have:

const { expect } = require("chai");

describe("SumOfTokens", function() { it("Checks correct transfers", async function() { const SumOfTokens = await ethers.getContractFactory("SumOfTokens"); const sumOfTokens = await SumOfTokens.deploy();

await sumOfTokens.deployed();

console.log(await sumOfTokens.newToken());

}); });

It prints some nonsense instead of the return value (that should be 1) of the external non-view function newToken.

How to obtain the return value of newToken after its call?

The Renaissance
  • 2,749
  • 1
  • 17
  • 45
porton
  • 1,774
  • 5
  • 17
  • 38
  • 1
    https://ethereum.stackexchange.com/questions/87643/listening-to-contract-events-using-ethers-js can help. In addition to goodvibration's answer, this might also help with the concepts: https://ethereum.stackexchange.com/questions/765/what-is-the-difference-between-a-transaction-and-a-call – eth Oct 07 '20 at 07:31

3 Answers3

52

The return value of a non-pure non-view function is available only when the function is called and validated on-chain.

When you call such function off-chain (e.g. from an ethers.js script), you need to execute it within a transaction, and the "return value" is the hash of that transaction.

This is because it is unknown when the transaction will be validated and added to the blockchain.

It used to be true that transactions could be removed from the blockchain. However, post-merge, this is no longer the case: once an epoch finalizes, it cannot be reverted.

Here's an example of how to retrieve and decode the data:

  const txData = sumOfTokens.interface.encodeFunctionData("newToken");
  const txResult = await provider.call({
    to: sumOfTokens.address,
    data: txData
  });
  const decodedResult = ethers.utils.defaultAbiCoder.decode("uint256", txResult);

Make sure to define the provider and import ethers. In this example, we assumed the returned 1 is of type 'uint256', so adjust the decoding according to the actual type.

Zakrad
  • 5
  • 2
goodvibration
  • 26,003
  • 5
  • 46
  • 86
  • 4
    Some questions: 1. All the troubles with return you told are also pertinent to events (events also need to be confirmed). So can I nevertheless obtain the return value (after enough confirmations)? How? 2. How to wait for 12 confirmations (not "12 blocks"... I think you have a typo?) and then to obtain the event? Please show the code. I am used to web3.js but not ethers.js – porton Oct 07 '20 at 04:41
  • 1
    "The return-value of a non-constant (neither pure nor view) function is available only when the function is called on-chain (i.e., from this contract or from another contract)." This is just plain incorrect. If there's a non-constant function you should be able to eth_call (off-chain) it to get the return value. This would obviously not modify storage permanently, but you should still be able to retrieve the return value(s). – Jonas Hals Sep 01 '21 at 08:06
  • This is helpful and educational, but the other answer mentioning callStatic is definitely worth mentioning. – Kyle Baker Oct 19 '21 at 16:38
27

While the return value of a function call executed on-chain cannot be returned off-chain, you can however simulate a function call on-chain to see what that function call would return.

In ethers, you can use callStatic. From ethers' documentation:

Rather than executing the state-change of a transaction, it is possible to ask a node to pretend that a call is not state-changing and return the result.

This does not actually change any state, but is free. This in some cases can be used to determine if a transaction will fail or succeed.

This otherwise functions the same as a Read-Only Method.

In your example, to simulate what sumOfTokens.newToken() would return if executed on-chain, you can do this:

console.log(await sumOfTokens.callStatic.newToken());
Jonas Hals
  • 490
  • 4
  • 5
  • 2
    oh man, thanks fo much for this answer, this is exactly what I needed. The accepted answer makes it sound impossible. – Kyle Baker Oct 19 '21 at 16:37
  • This is the way to do it. Thank! – Steven K Dec 27 '21 at 23:26
  • If the function in question is a factory function (i.e. it calls new to create a new contract) and the return value you are seeking is the address of the newly created contract, will call static return the address of the contract that -will- be created if the contract is called normally? – GGizmos Apr 18 '22 at 10:16
  • @GGizmos Call static will return the return values of the function. So unless the factory function explicitly returns the address of the newly created contract, you're not going to be able to get it from callStatic. There are however other ways to predict the address of new contracts: https://docs.openzeppelin.com/cli/2.8/deploying-with-create2#creating_a_smart_contract – Jonas Hals Apr 25 '22 at 22:07
11

Here is an example using Events:

const tx = await contract.transfer(...args); // 100ms
const rc = await tx.wait(); // 0ms, as tx is already confirmed
const event = rc.events.find(event => event.event === 'Transfer');
const [from, to, value] = event.args;   
console.log(from, to, value); 

More info https://ethereum.stackexchange.com/a/119856/92472

Carlitos
  • 346
  • 3
  • 5