4

I've been reading a lot about upgradability of smart contracts, and it seems like major prevailing practice is that it is generally a good idea to keep contract logic separate from contract storage, so it is possible to upgrade one and not have to change the other one.

My question is the following: does the fact that one contract has to call another contract increase the amount of gas that is spent by that logic. For example, say I had a contract that looked up a number X and then added it to the number 5.

In one case, the contract stores in X itself, and so it can directly look it up. In the other case, a separate contract is responsible for storing X, causing one contract to make a function call to get X. Does one cost more gas? If so, is it possible to tell how much more?

Thanks!

3

I would say yes, a little, but no it's not terribly important in most cases. Setting aside one-time deployment cost, there will be additional code to run with each transaction. It's not free, but the additional code won't be especially more expensive to operate in most cases.

Have a look at gas costs per operation (no warranty implied): https://docs.google.com/spreadsheets/d/15wghZr-Z6sRSMdmRmhls9dVXTOpxKy8Y64oy9MvDZEQ/edit#gid=0.

A basic heuristic is that state change opcodes (e.g. SSTORE) are quite high compared to compute opcodes that don't change the blockchain. One can generally add additional low-cost overhead steps (e.g. look up a contract address and form a message) without greatly increasing the overall cost of a state change. The state change will be expensive in any case.

Read operations are often handled with constant functions or local calls that don't use gas at all. So, transaction cost concerns are generally limited to transactions that will result in a state change. So, create/insert, update and delete have a cost (the lion's share from ops like SSTORE). Reads are usually cost-free regardless of complexity. Also consider that many systems use offchain storage fed by event emitters that maintain a copy of "official" facts for performance reasons. Another example of a situation in which only the cost of updates is of any concern at all.

To play around with a specific example and compare costs, you could consider coding representative functions both ways and comparing actual gas consumption. Solidity Realtime compiler will show the bytecode after compilation. You can step over source code to see which actual steps are "expensive" and optimize accordingly.

As a footnote, beware of the hidden cost of additional complexity. While upgradable contracts are advantageous in many ways, there's a yin-yan-like set of tradeoffs to consider for each use-case. For example, upgradability could reduce trustworthyness in some cases and is potentially a new source of bugs.

Hope it's helpful.

  • As noted in the answer below, there's a significant gas cost of 700 per library call, as compared to 10 for a JUMP operation. – JS_Riddler Mar 17 '18 at 8:16
  • I see a message for a view function call in Remix that states "(Cost only applies when called by a contract)". I have an address balance checking function, which makes a call to an external token contract using the Erc20 balanceOf method. So in this case, the token contract is in fact called by my smart contract. Will this cost gas as stated, even though the entire process is read-only? – GViz Jun 12 '18 at 3:15
2

Yes, but it's advisable to profile against the tradeoffs of the increased gas costs, with the upgradability it provides to your application.

Most upgradeable contracts will involve libraries and the DELEGATECALL opcode and some of the costs are described in part of the Solidity docs on Libraries:

Libraries can be seen as implicit base contracts of the contracts that use them. They will not be explicitly visible in the inheritance hierarchy, but calls to library functions look just like calls to functions of explicit base contracts (L.f() if L is the name of the library). Furthermore, internal functions of libraries are visible in all contracts, just as if the library were a base contract. Of course, calls to internal functions use the internal calling convention, which means that all internal types can be passed and memory types will be passed by reference and not copied. In order to realise this in the EVM, code of internal library functions (and all functions called from therein) will be pulled into the calling contract and a regular JUMP call will be used instead of a DELEGATECALL.

Basically when everything is in a contract, it can use references, avoid copies, and use the JUMP opcode.

When external contracts are involved, DELEGATECALL is much more expensive than JUMP (currently 700 gas vs 10 gas), and there are other costs such as copying data.

But it's important to also profile the specific use case and compare. For the simple example given above, the cost of a CALL/DELEGATECALL would be overwhelming. But in other use cases, these additional gas costs may have a much smaller impact overall.

  • You made me shy away from answering "No" ... I'm thinking yes, compute costs are higher, but since we're talking about storage, compute cost is dwarfed by state change cost in most cases. In the case of reads, local calls don't cost gas. – Rob Hitchens - B9lab Jan 21 '17 at 23:29
  • @RobHitchens I think your answer is good and I would upvote it: we're presenting different considerations and perspectives and I don't see much incongruity. (I agree early answers can influence additional ones: not sure how to reduce it; sorry that it had the effect of shying away, especially like a well-written answer as yours.) – eth Jan 22 '17 at 2:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.