4

I am developing smart contracts for some use-cases and currently I'm working on optimization of smart contracts. I am confused with something that I found interesting on Hitchiker's Guide. In the section 4- Iterating the contract code

// returns true if proof is stored
// *read-only function*
function hasProof(bytes32 proof) constant returns (bool) {
for (uint256 i = 0; i < proofs.length; i++) {
    if (proofs[i] == proof) {
        return true;
    }
}
return false;
}

For this code above, He states that "Note that every time we want to check if a document was notarized, we need to iterate through all existing proofs. This makes the contract spend more and more gas on each check as more documents are added. "

There is no doubt that the proper way of implementing it is to use mapping instead of array structure. Here is the point that makes me confused. It's read-only function and it is not a transaction that affects blockchain. When I observed my netstats, it does not show any transaction when this function is called(Actually, it is what I expected before calling this function).

I don't think that he misunderstood the mechanism, could someone clean my mind about this comment?

Mehmet Doğan
  • 186
  • 2
  • 8

4 Answers4

8

Roman’s answer is not correct. Constant functions still consume gas. However, you don’t pay for the gas usage when it runs in the local EVM. If a constant function is called from a transaction, it is not free. Either way, you are still consuming gas and loops are a good way to consume a lot.

EDIT - Here is an example to illustrate the point

pragma solidity ^0.4.19;

contract LoopExample {
  bytes32[] proofs;
  
  function addProof(bytes32 proof) public {
    if (!hasProof(proof))
      proofs.push(proof);
  }
  
  function hasProof(bytes32 proof) public constant returns (bool) {
    for (uint256 i = 0; i < proofs.length; i++) {
      if (proofs[i] == proof) {
        return true;
      }
    }

    return false;
  }
}

And here are the gas consumption results for calling addProof 4 times:

addProof("a"): 41226

addProof("b"): 27023

addProof("c"): 27820

addProof("d"): 28617

You kind of have to ignore the very first call. The reason that one is more expense than the rest is because the very first push to proofs will cost more (no storage slot is used before the 1st call, so the push will cost 20000 gas). So, the relevant part for this question is to look at the cost of addProof("b") and then the increase with each call afterwards. The more items you add, the more gas the loop will use and eventually you will hit an out of gas exception.

Here is another example where you are only calling a constant function from the client:

pragma solidity ^0.4.19;

contract LoopExample {
  function constantLoop(uint256 iterations) public constant {
    uint256 someVal;
    
    for (uint256 i = 0; i < iterations; i++) {
      someVal = uint256(keccak256(now, i));
    }
  }
}

Here, if you call this through Remix, you'll see something like this in the output (Notice the comment on gas usage):

Remix Output Screencap

Finally, if you try to run this constant method from a client using too many iterations, you will get an error:

$ truffle console
truffle(development)> let contract;
undefined
truffle(development)> LoopExample.deployed().then(function(i) { contract = i; });
undefined
truffle(development)> contract.constantLoop.call(999);
[]
truffle(development)> contract.constantLoop.call(9999);
[]
truffle(development)> contract.constantLoop.call(99999);
Error: VM Exception while processing transaction: out of gas
    at Object.InvalidResponse (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\errors.js:38:1)
    at C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\requestmanager.js:86:1
    at C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\truffle-provider\wrapper.js:134:1
    at XMLHttpRequest.request.onreadystatechange (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\httpprovider.js:128:1)
    at XMLHttpRequestEventTarget.dispatchEvent (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:64:1)
    at XMLHttpRequest._setReadyState (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:354:1)
    at XMLHttpRequest._onHttpResponseEnd (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:509:1)
    at IncomingMessage.<anonymous> (C:\Users\adamk\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:469:1)
    at emitNone (events.js:91:20)
    at IncomingMessage.emit (events.js:185:7)
Community
  • 1
  • 1
Adam Kipnis
  • 10,175
  • 10
  • 35
  • 48
  • Your answer seems most valid up to now but It did not resolve my question clearly. Can you give more insights about "If a constant function is called from a transaction ...". What do you mean by sample ? Thanks, – Mehmet Doğan Feb 13 '18 at 16:49
  • You are absolutely correct. I was thinking about it from the wrong perspective. – Roman Frolov Feb 13 '18 at 17:37
  • @MehmetDoğan it means that you can call this function on your local machine and it will be free, but if this constant function will be called inside already executing transaction on miners node it will cost you gas fee, because those computations won’t be happening on your local machine as in the individual `.call()`. – Roman Frolov Feb 13 '18 at 17:44
  • 1
    @MehmetDoğan - Please see the edit for examples. Hopefully that will clear things up. – Adam Kipnis Feb 13 '18 at 18:57
  • In case we do need this kind of function, then how do we pay gas for this case? – NgocTP Aug 10 '21 at 08:52
  • @NgocTP - Do you mean outside of a transaction? You don't. It doesn't matter if you're within a transaction or outside of it, you have to specify a gas limit high enough to cover the consumption. The only difference is you don't actually pay for the gas usage unless you're within a transaction. – Adam Kipnis Aug 10 '21 at 21:09
2

Most of the people will say constant functions will not consume gas. That will not correct because, constant functions execute on the local node's hardware using it's own copy of the blockchain. This makes the actions inherently read-only because they are never actually broadcast to the network.

Lets assume I've contract having isEmp constant function, when I call estimateGas() it should return 0 if constant method's are not consuming gas. But its returning 23301.

truffle> contractObj.isEmp.estimateGas(web3.eth.accounts[0])

23301

Here is my solidity code

function isEmp(address employee) public constant returns(bool) {
    return  emps[employee];  
}

Hence above experiment is proved that it will consume gas.

Jitendra Kumar. Balla
  • 1,173
  • 1
  • 9
  • 15
1

The thing is that constant (now it's called view) functions are getting the information from previously executed normal functions. Why? Because view functions are accessing the storage of the contract that created in creation of the contract and changed with functions that changes it if you write the function that way. For example:

  contract calculateTotal{
    
   uint256 balance;

  constructor(uint256 _initialBalance){

       balance=_initialBalance;

       }

  function  purchaseSmth(uint256 _cost) external {

    balance=balance-_cost;

   }

  function getBalance() external view returns(uint256){

     return balance;
   }


}

Now when you create this contract you set the balance, that way balance already calculated and you don't need to be recalculated because it will be the same. When you change that balance value with purchaseSmth the balance will be recalculated and storage value of balance is going to change. That means that function will change the blockchain's storage. But again you are paying for these executions because the blockchain must be changed. But view functions are not changing any storage value. They are just getting the previously calculated value that standing in blockchain already. When you make some calculations in the view functions like: return balance*10; the execution does not affects blockchain. The calculation of it will be done by your device. Not by any miner.

If we look at the answer of @AdamKipnis we see that constant/view function is not consuming any gas by itself if you call it rightaway. Because it will be always calculated before. First the constructor will create it as empty array. Then when you call the hasProof you will see an empty array. Then when you use addProof it will change the proofArray. But the price is going bigger because it uses the hasProof function in it. If it wasn't there then the price would not go up forever. However If you call the hasProof function manually then your computer(if it is a node) will execute that loop locally eitherway if that function has it or not. But any storage, status changing execution will be directed to miners because of the blockchain logic. Someone else should execute that code and verify it If that change is possible in that transaction and If there is no error in the code.

When you use a wallet like metamask wallet in some cases you can see the errors before making any transactions(executions). But that is not a verified error until you make the transaction and a miner executes that code then finds that this code has some errors or it is doing, changing something in a way that it shouldnt be possible. Like trying to transferring to yourself some bnb from another person's wallet. That execution can't happen because It is not your wallet to transfer the bnb from. It sounds obvious but random miners should validate that execution to making a block that includes your transaction in it with other executions in per block transaction limit boundary in that time.

EDIT: The device, computer, hardware is the node. Node can be you or if you use a remote node for your executions than the node will do everything your device will not. Like cloud computing. Because you would'nt have a ledger if you were not a node.

1

It will only cost gas if the constant (view or pure) function is executed or called by another external smart contract, that is not the owner of that function. But if it is called from within the smart contract that declared the constant (view or pure) function, then no gas will be used.

Just like when you use a class member function in another class in other programming languages by creating an object of that class. But in solidity this will cost you regardless of the fact that it's a constant function or not.

So when Jitendra Kumar. Balla did an experiment that showed it will cost gas, the gas shown there is the estimated gas cost if it is called by another smart contract but not from within the smart contract.

Benjamin
  • 133
  • 7