Solidity – Solving ‘Function Cannot Be Declared as View Due to Potential State Modification’

functionview

Compiling the contract returns this error.

 TypeError: Function cannot be declared as view because this expression (potentially) modifies the state.
       --> contracts/certification/token/CleanEnergyCertificateToken.sol:104:9:
        |
    104 |         contractAddress.call(abi.encodeWithSignature("getTokenProperties(uint256 tokenId)", tokenID));
        |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The issue is that obvious: getTokenProperties() function's structure is unknown to the compiler in this case, because I am using call. So it doesn't know that getTokenProperties() is a view function that doesn't modify storage.

Here is getTokenProperties() code for reference:

function getTokenProperties(uint256 tokenId) public view returns (CleanEnergyCertificateTokenProperties memory)  {
    require(
        tokenId >= 0 && tokenIdToProperties[tokenId].timestamp > 0,
        "Token and it's properties for with respective tokenId must exist!"
    );
    return tokenIdToProperties[tokenId];
}

Therefor the only solution here seems to be to leave out the "view" modifier.

Question: Will getTokenProperties() spend gas if it has the functionality of a real "view" function, but it isn't declared as such.

EDIT: I just figured out I can use staticcall instead of call and that solves the issue.

But My question still remains. What if I forget to declare function and view ? Will it spend gas? I guess not, but I am not sure.

Best Answer

There are a few different perspectives into this. Let's see.

Blockchain's perspective

The blockchain basically just stores data. Data can be read from any node. State changes aren't free, reading data from a node is free. The blockchain doesn't know if someone reads data locally from a node - it only cares if someone issues a transaction to change the blockchain state, and that costs.

Tooling

Tooling around the blockchain provide different ways of calling contracts' functionality. Modifying the blockchain state always requires a real transaction. But it's also fine to issue a static call to non-view functions - in that case the wannabe-transaction is issued only to your local node (or whichever node provider you use) and the state changes it performs are not persisted - but its operations are free.

One issue with tooling is that it typically relies on various 'hints' about how to call different functionality. For example if you want to call a non-view function, tooling typically offers you to issue a real transaction.

Most tooling (for example ethers.js) offer ways to call whichever function with whichever type - a local/static call or with a real transaction. It just may not be the most straightforward way, since the tooling tries to make things easy for you (by assuming you always want a real transaction to a non-view function).

Wallets

These are often quite dumbed-down versions of tooling, but a lot more user friendly. It may be difficult, if not impossible, to configure your wallet to issue a read-only call to a non-view function, for example. This of course applies only to functionality which is not triggered by various tooling (for example by a website running ethers.js).

Conclusion

In theory it's fully up to the caller whether to issue a real transaction or a local read-only call. In reality it depends on the used tools, whether they support that and only provide what they think you want.

Related Topic