43

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?

1

3 Answers 3

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.

3
  • 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 7, 2020 at 4: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 1, 2021 at 8:06
  • This is helpful and educational, but the other answer mentioning callStatic is definitely worth mentioning.
    – Kyle Baker
    Oct 19, 2021 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());
4
  • 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, 2021 at 16:37
  • This is the way to do it. Thank!
    – Steven K
    Dec 27, 2021 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, 2022 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: docs.openzeppelin.com/cli/2.8/…
    – Jonas Hals
    Apr 25, 2022 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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.