🐶
Solidity

Get Return Values from Solidity Smart Contract Functions

By Ondřej Dolanský on 12/01/2024

Learn how to retrieve and process return values from Solidity smart contract functions in your web3 applications.

Get Return Values from Solidity Smart Contract Functions

Table of Contents

Introduction

When interacting with Solidity contracts, it's crucial to understand that you can't instantly retrieve the return value of a function that alters the blockchain's state. This is because such changes require the transaction to be processed and added to the blockchain, a process that takes time.

Step-by-Step Guide

You can't directly access the return value of a Solidity function that modifies the blockchain state (a "transaction") from outside the EVM (Ethereum Virtual Machine). This is because the transaction needs to be mined and added to a block before its effects are reflected on the blockchain.

Let's illustrate with a simple contract:

contract MyContract {
    uint256 public myValue;

    function setValue(uint256 _newValue) public {
        myValue = _newValue;
    }

    function getValue() public view returns (uint256) {
        return myValue;
    }
}

If you call setValue from your web3 code, you won't immediately get the new value back. Instead, you'll get a transaction receipt.

To retrieve the updated value, you have two main options:

1. Events:

  • Define an event inside your contract:

    event ValueUpdated(uint256 newValue);
  • Emit the event within your function:

    function setValue(uint256 _newValue) public {
        myValue = _newValue;
        emit ValueUpdated(_newValue);
    }
  • Listen for the event in your web3 code:

    myContract.events.ValueUpdated({}, (error, event) => {
        console.log("New value:", event.returnValues.newValue);
    });

2. Getter Functions:

  • Create a separate function (often marked view or pure) to read the value:

    function getValue() public view returns (uint256) {
        return myValue;
    }
  • Call this getter function after the transaction is mined:

    await myContract.methods.setValue(123).send({ /* ... */ });
    const newValue = await myContract.methods.getValue().call();
    console.log("New value:", newValue);

Remember that events are logged on the blockchain and can be more expensive than getter functions, but they provide a historical record of changes. Getter functions are cheaper but only give you the current state.

Code Example

This code demonstrates a simple smart contract written in Solidity and how to interact with it from a JavaScript application using ethers.js. The contract allows updating and retrieving a single value. The JavaScript code showcases two methods for accessing the updated value: listening to events emitted by the contract and calling a getter function.

pragma solidity ^0.8.0;

contract MyContract {
    uint256 public myValue;

    event ValueUpdated(uint256 newValue);

    function setValue(uint256 _newValue) public {
        myValue = _newValue;
        emit ValueUpdated(_newValue);
    }

    function getValue() public view returns (uint256) {
        return myValue;
    }
}

JavaScript (using ethers.js)

const { ethers } = require("ethers");

// Replace with your contract address and ABI
const contractAddress = "0x..."; 
const contractABI = [...]; 

// Connect to your Ethereum provider
const provider = new ethers.providers.JsonRpcProvider("..."); 
const signer = provider.getSigner();

// Create a contract instance
const myContract = new ethers.Contract(contractAddress, contractABI, signer);

async function main() {
  // Option 1: Using Events
  myContract.on("ValueUpdated", (newValue) => {
    console.log("New value from event:", newValue.toString());
  });

  // Option 2: Using Getter Function
  const tx = await myContract.setValue(123);
  await tx.wait(); // Wait for transaction to be mined
  const newValue = await myContract.getValue();
  console.log("New value from getter:", newValue.toString());
}

main();

Explanation:

  1. Solidity Contract:

    • The contract defines a state variable myValue, an event ValueUpdated, and two functions: setValue and getValue.
    • setValue modifies the myValue and emits the ValueUpdated event.
    • getValue is a view function that simply returns the current value of myValue.
  2. JavaScript Code:

    • We use ethers.js to interact with the contract.
    • Option 1 (Events):
      • We use myContract.on("ValueUpdated", ...) to listen for the ValueUpdated event.
      • The callback function will be executed whenever the event is emitted.
    • Option 2 (Getter Function):
      • We call myContract.setValue(123) to set the new value.
      • We use tx.wait() to wait for the transaction to be mined and confirmed.
      • We call myContract.getValue() to retrieve the updated value.

This example demonstrates both methods for accessing the updated state after a transaction. Choose the method that best suits your needs based on whether you need a historical record (events) or just the current state (getter functions).

Additional Notes

Understanding the Blockchain:

  • Asynchronous Nature: Blockchain interactions are inherently asynchronous. When you send a transaction, it doesn't execute immediately. It gets added to a block and then mined, which takes time.
  • EVM Execution: The EVM executes your Solidity code within its own environment. Directly accessing return values from outside this environment isn't possible until the transaction is confirmed on the blockchain.

Choosing Between Events and Getters:

  • Real-time Updates: If you need real-time updates in your application (e.g., updating a UI as soon as a value changes), events are a better choice.
  • Cost Efficiency: Getter functions are generally more cost-effective if you only need to retrieve the value at a specific point in time.
  • Historical Data: Events provide an on-chain record of changes, which can be useful for auditing or tracking historical data.

Gas Considerations:

  • Events and Gas: Emitting events consumes gas, and storing event data on the blockchain increases storage costs. Be mindful of this when designing your contract.
  • View Functions and Gas: view and pure functions don't modify the blockchain state and can be called without spending gas (as long as they are called off-chain or within another function call).

Error Handling:

  • Transaction Failures: Always handle potential errors when sending transactions. A transaction can fail for various reasons (e.g., insufficient gas, contract revert).
  • Event Listeners: Implement error handling in your event listeners to gracefully manage cases where events might be missed or there are network issues.

Best Practices:

  • Clear Documentation: Clearly document your contract's functions and events, specifying whether they modify the blockchain state and how to retrieve any relevant data.
  • Testing: Thoroughly test your contract interactions, including both event handling and getter function calls, to ensure they behave as expected.

Summary

This article explains why you can't directly access the return value of a Solidity function that modifies the blockchain state from outside the EVM.

Key Points:

  • Transactions take time: Changes made by a function call need to be mined and added to a block before they are reflected on the blockchain.
  • Two ways to retrieve updated values:
    • Events:
      • Define an event inside your contract.
      • Emit the event within your function when the value changes.
      • Listen for the event in your web3 code to get the new value.
    • Getter Functions:
      • Create a separate view or pure function to read the updated value.
      • Call this getter function after the transaction is mined.
  • Events vs. Getter Functions:
    • Events are logged on the blockchain, providing a historical record, but are more expensive.
    • Getter functions are cheaper but only provide the current state.

In essence: You need to use events or getter functions to retrieve updated values from Solidity functions that modify the blockchain state because the changes are not immediately reflected.

Conclusion

To interact effectively with smart contracts on the blockchain, it's essential to understand the asynchronous nature of transactions and how to retrieve data after state changes. While you can't directly access return values from functions that modify the blockchain, events and getter functions provide viable solutions. Events offer a historical log of changes, suitable for real-time updates and data tracking, but come with a gas cost. Getter functions, on the other hand, are more cost-effective for retrieving the current state. By carefully considering the trade-offs and implementing appropriate error handling, developers can build robust and efficient decentralized applications.

References

Were You Able to Follow the Instructions?

😍Love it!
😊Yes
😐Meh-gical
😞No
🤮Clickbait