Let me start with basics of contract code execution. Smart contracts run on the machines of miners, who can earn Ethers by contributing their computing resources. The creators and users of smart contracts will be charged a certain amount of gas for purchasing the computing resources from
miners. So 'Gas' measures how much 'work' a set of actions takes to perform. For example, to perform ADD/SUB operation, it takes 3 gas and for MUL/DIV, it takes 5 gas. There are other expensive operations like SLOAD, STORE as well, which consumes 200 and 5000 gas respectively. The charge is nothing but the 'Transaction cost' which is calculated by multiplying Gas and Gas Price. A developer has to be very careful while writing these contracts, otherwise, the user/creator of those contracts may end up losing lots of Gas. And sometime, it may exceed the block gas limit as well and in such cases, those contracts won't even get deployed on the network. To read about 'block gas limit' concept, please look here. In short it defines the maximum amount of gas all transactions in the whole block combined are allowed to consume. The developer must know about these operations and how much they consume gas.
Bottom line is to write optimized contract code. And to do it, the developer should be familiar with 'opcodes'. Let's look at some of these instructions and understand their behavior.
- Stack operations (e.g., POP, PUSH), arithmetic operations (e.g., ADD, SUB), bitwise operations (e.g., OR, XOR), and comparison operations (e.g., LT/GT) are cheap because being a stack-based virtual machine, EVM favors such stack-related operations.
- Loading a word (i.e., 256 bits) from the memory (e.g., MLOAD) or saving a word to the memory (e.g., MSTORE) are also cheap. For those who don’t know what memory is in solidity, please refer to the solidity document. And search for data allocation (3 types basically, storage, memory, calldata).
- It is worth noting that the gas consumption will be multiplied if many words in memory are read or written.
- Loading a word from the storage (i.e., SLOAD) or saving a word to the storage (i.e., SSTORE) are expensive.
- Memory is temporary but the storage is the persistent memory.
- A SSTORE operation costs 20,000 units of gas if the storage word is set to non-zero from zero; otherwise, it costs 5,000.
- EVM has a number of block chain-specific operations which are very expensive, such as BALANCE, CREATE and CALL.
- A conditional jump (i.e, JUMPI) is more expensive than an unconditional jump (i.e., JUMP).
How should we write these contracts, so that it won’t consume much gas?
- Understanding of EVM’s instructions properly: if we know these instructions and understand their usage, we can definitely write our codes in such a way that it will consume less gas.
- Using data location (memory, storage, and stack) wisely: Whenever we use data locations, we have to use them wisely otherwise we may end up losing gas.
- We should avoid loops and recursions.
- We should also focus on the amount of data read or written to or from blockchain.
- It is worth noting the cost of storing data on blockchain. According to the ethereum yellow paper, the fee is the 20k gas to store a 256-bit word. A kilobyte is thus 640k gas.Gas right now is around 50 Gwei (0.00000005 ETH). So a KB of storage costs 0.032 ETH which roughly equals to $16. Hence developers should only store critical data on blockchain like id or hash key of an object. Other properties like title, description etc can be stored and fetched off-chain.
- We should also be very careful while using data structures and datatypes in contracts code. Use mapping (as far as possible) instead of an array. Also 'bytes32' is cheaper as compared to 'string' type. If you can limit the length to a certain number of bytes, always use one of bytes1 to bytes32 because they are much cheaper.
- After writing contracts, we must, not only deploy and test them on testrpc but also test them on own private network using Geth console. One may get block limit exceeded error on the private network which he did not get on testrpc. In that case, a developer has to optimize the contract code.
- We can use Remix browser to estimate gas consumption before deploying to contracts on the network. It provides a very rich interface to write, test and deploy contracts codes in different environments.
I would like to conclude this discussion by saying that, don't pretend contracts code as a normal code which you may have written in the java, c# or other languages. Since the executions of the contract codes need real money, their behavior becomes completely different from other.