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)  {
        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.

1 Answer 1


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 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).


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).


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.

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.