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.