Smart contract storage can be used to read from smart contract state variables and to retrieve stored information. This could be anything from user balances, configuration parameters, or complex data structures such as mappings and arrays.

In this guide, we will walk through:

  • How smart contract storage works in general.
  • Common scenarios where you need to retrieve data from contract storage.
  • Step-by-step instructions on how to fetch data, using Solidity and a client library (e.g., web3.js or ethers.js).
  • Best practices and potential pitfalls.

By the end, you will understand how to confidently and securely fetch data from your peaq-based smart contracts.

Prerequisites

  • Understanding of Solidity
  • You have a working development environment set up with:
    • A Solidity compiler (e.g., via Hardhat, Truffle, or Remix).
    • Access to the peaq network’s EVM endpoint (or a local test EVM).
  • Using a library like web3.js or ethers.js to interact with your deployed contracts. Alternatively, any other JSON-RPC client can be used if you are comfortable with lower-level interactions.
  • Already have a deployed contract on the peaq network, or you have the means to deploy one during testing.
  • Access to contract’s its Application Binary Interface (ABI). This is generally generated automatically when compiling your Solidity smart contract.

Instructions

1. Understanding EVM Contract Storage

In EVM-based networks like peaq, each contract has its own allocated storage. Storage is typically broken down into slots of 256 bits each:

  • State Variables store data in these storage slots.
  • Mappings are hashed to find their storage slots.
  • Arrays and other data structures have their own packing and storage rules.

When writing or reading from storage, gas costs and efficient layout (for writes) can be important. However, retrieving data (a read) is cheaper than storing data (a write). For reads, you can query the node’s state without incurring on-chain transaction costs.

2. Declaring and Storing Data in Solidity

Let’s look at a simple example contract that stores some data:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleStorage {
    uint256 public storedValue;
    mapping(address => uint256) public balances;

    constructor(uint256 initialValue) {
        storedValue = initialValue;
        balances[msg.sender] = 100;
    }

    // A simple setter function
    function setValue(uint256 newValue) external {
        storedValue = newValue;
    }

    // A simple function that updates balances
    function updateBalance(address account, uint256 amount) external {
        balances[account] = amount;
    }
}

  • storedValue is a public uint256. By making it public, Solidity automatically creates a getter function storedValue() that returns the value.
  • balances is a public mapping from address to uint256. Likewise, balances(address) becomes a getter function to read the balance for any given address.

3. Interacting With the Contract to Fetch Data

Using a Script (JavaScript + ethers.js/web3.js)

Below is an example of how to fetch data from storedValue and balances using ethers.js. Ethers.js Example:

import { ethers } from 'ethers';

// 1. Connect to the peaq network (replace with actual provider endpoint)
const provider = new ethers.JsonRpcProvider("https://your-peaq-rpc-endpoint");

// 2. Define the contract ABI (simplified for this example)
const abi = [
  "function storedValue() view returns (uint256)",
  "function balances(address) view returns (uint256)"
];

// 3. The deployed contract address
const contractAddress = "0xYourContractAddressOnPeaq";

// 4. Create an instance of the contract
const contract = new ethers.Contract(contractAddress, abi, provider);

async function readData() {
  try {
    // 5. Call the view functions
    const value = await contract.storedValue();
    console.log("storedValue:", value.toString());

    const someAddress = "0x1234..."; // Replace with a valid address
    const balance = await contract.balances(someAddress);
    console.log(`Balance of ${someAddress}:`, balance.toString());
  } catch (error) {
    console.error("Error fetching data:", error);
  }
}

readData();

Key Points

  • We use the view functions (storedValue() and balances(address)) to retrieve data without sending a transaction.
  • This means there is no cost in terms of gas for these read operations; only the provider’s request/response overhead applies.

Using the Remix IDE

If you prefer a browser-based approach:

  1. Open Remix IDE.
  2. Select the “Solidity” environment and load your contract code.
  3. Compile and Deploy to a peaq network-compatible endpoint (via “Deploy & run transactions” panel).
  4. After deployment, you will see the contract’s interface in Remix.
    • Clicking on storedValue will fetch and display the current storedValue.
    • Providing an address to balances will fetch and display the mapping value for that address.

4. Fetching Custom or Complex Data Types

If you have custom data structures like structs or nested mappings, you can expose them through custom getter functions. For example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ComplexStorage {
    struct User {
        uint256 id;
        uint256 balance;
        address userAddress;
    }

    mapping(address => User) private users;

    // Insert or update user
    function setUser(address _address, uint256 _id, uint256 _balance) external {
        users[_address] = User(_id, _balance, _address);
    }

    // Public function to read user data
    function getUser(address _address) external view returns (uint256, uint256, address) {
        User memory user = users[_address];
        return (user.id, user.balance, user.userAddress);
    }
}

Here, the struct User is stored in a private mapping. We expose a getUser function that returns the data in a single call. You can fetch it from your JavaScript code similarly with a function call:

const contractAbi = [
  "function getUser(address _address) view returns (uint256, uint256, address)"
];

const contract = new ethers.Contract(contractAddress, contractAbi, provider);

const userData = await contract.getUser(someUserAddress);
console.log("User Data => ID:", userData[0].toString(), 
            "Balance:", userData[1].toString(), 
            "User Address:", userData[2]);

This approach helps keep your contract’s data private while still allowing necessary read-access to data via dedicated functions.

Summary

Fetching data from smart contract storage on the peaq network (or any EVM-compatible network) follows the same paradigm as Ethereum:

  • You define state variables in Solidity.
  • You expose those variables either with the public keyword or dedicated getter functions.
  • You interact with them through a client library (e.g., ethers.js, web3.js) or Remix IDE.

Key Takeaways:

  • Public Variables: Automatically generate getter functions, convenient for straightforward retrieval.
  • View/Pure Functions: Cost no gas when called off-chain, making them ideal for reading data.
  • Custom Getter Functions: Useful for complex data structures or additional data processing.
  • Security: Ensure that any data you do not want freely accessible remains in private variables, and only expose it through controlled view functions as needed.

With these core principles in mind, you can confidently fetch data from your peaq-based Solidity contracts, enabling rich dApp functionality and user-friendly front-end experiences.