45

As we all know, there are many factors that determine a good smart contract, such as:

  • Security: it has minimal/zero vulnerability so they cannot be exploited by an adversary. Immune to Attacks.

  • Cost: how much in total a

      (a) smart contract deployment costs, 
    
      (b) running/invoking each function of it costs. 
    
  • Correctness: it executes as it has been planned.


In this question, I'd like to focus on the cost of a smart contract.

Question 1: How to make a cost-effective smart contract?

Question 2: How to avoid using some expensive functions and what are the alternatives? In other words, are there any functions well-known to be more expensive than other operations and can we replace them with better ones?

Question 3: In general, what are the good practice of writing a smart contract with minimal cost?

In short: How can we save gas : ether : money?

1
  • 2
    as additional information use event(log) to store your data.
    – Badr Bellaj
    Oct 20, 2017 at 19:27

3 Answers 3

64

In Ethereum, transactions cost gas and hence ether. The gas consumption of a transaction depends on the opcodes that the EVM has to execute. The gas cost for each Opcode can be found as explained in this question. Few common opcodes and gas are,

Operation         Gas           Description

ADD/SUB           3             Arithmetic operation
MUL/DIV           5             Arithmetic operation
ADDMOD/MULMOD     8             Arithmetic operation
AND/OR/XOR        3             Bitwise logic operation
LT/GT/SLT/SGT/EQ  3             Comparison operation
POP               2             Stack operation 
PUSH/DUP/SWAP     3             Stack operation
MLOAD/MSTORE      3             Memory operation
JUMP              8             Unconditional jump
JUMPI             10            Conditional jump
SLOAD             200           Storage operation
SSTORE            5,000/20,000  Storage operation
BALANCE           400           Get balance of an account
CREATE            32,000        Create a new account using CREATE
CALL              25,000        Create a new account using CALL

This is a concern when it comes to smart contracts as transactions are also involved and it's important to consider the gas cost when designing a contract.

Reducing the gas consumed by a contract is important in two situations,

  1. Cost of deploying a contract
  2. Cost to call the contract functions

Cost of deploying a contract

For this, most of the optimizations are done at compilation time as described in the documentation-faqs,

Are comments included with deployed contracts and do they increase deployment gas?

No, everything that is not needed for execution is removed during compilation. This includes, among others, comments, variable names and type names.

And the details of the optimizer can be found here.

Another way of reducing the size is by removing useless code. For example:

1 function p1 ( uint x ){ 
2    if ( x > 5)
3     if ( x*x < 20)
4        XXX }

In above code, line 3 and 4 will never be executed and these type of useless code can be avoided by carefully going through the contract logic and that will reduce the size of the smart contract.

Cost to call the contract functions

When contracts' functions are called, for the execution of function it costs gas. Hence optimizing functions to use less gas is important. There can be many different ways of doing it when individual contract is considered. Here are few that might save gas during execution,

  1. Reduce Expensive operations

Expensive operations are the opcodes that has more gas values such as SSTORE. Below are some methods of reducing expensive operations.

A) Use of Short Circuiting rules

The operators || and && apply the common short-circuiting rules. This means that in the expression f(x) || g(y), if f(x) evaluates to true, g(y) will not be evaluated even if it may have side-effects.

So if a logical operation includes an expensive operation and a low cost operation, arranging in a way that the expensive operation can be short circuited will reduce gas at some executions.

If f(x) is low cost and g(y) is expensive, arranging logical operations

  • OR : f(x) || g(y)
  • AND : f(x) && g(y)

will save more gas if short circuited.

If f(x) has a considerably higher probability of returning false compared to g(y), arranging AND operations as f(x) && g(y) might cause to save more gas in execution by short circuiting.

If f(x) has a considerably higher probability of returning true compared to g(y), arranging OR operations as f(x) || g(y) might cause to save more gas in execution by short circuiting.

B) expensive operations in a loop

eg:

 uint sum = 0;
 function p3 ( uint x ){
     for ( uint i = 0 ; i < x ; i++)
         sum += i; }

In the above code, since sum storage variable is read and written every time inside the loop, storage operations that are expensive take place at every iteration. This can be avoided by introducing a local variable as follows to save gas.

 uint sum = 0;
 function p3 ( uint x ){
     uint temp = 0;
     for ( uint i = 0 ; i < x ; i++)
         temp += i; }
     sum += temp;
  1. Other loop related patterns

loop combining

function p5 ( uint x ){
    uint m = 0;
    uint v = 0;
    for ( uint i = 0 ; i < x ; i++) //loop-1
        m += i;
    for ( uint j = 0 ; j < x ; j++) //loop-2
        v -= j; }

loop-1 and loop-2 can be combined and gas can be saved,

 function p5 ( uint x ){
    uint m = 0;
    uint v = 0;
    for ( uint i = 0 ; i < x ; i++) //loop-1
       m += i;
       v -= j; }

and a few more loop patterns can be found here.

  1. Using of Fixed-size bytes arrays

From Docs,

It is possible to use an array of bytes as byte[], but it is wasting a lot of space, 31 bytes every element, to be exact, when passing in calls. It is better to use bytes.

and

As a rule of thumb, use bytes for arbitrary-length raw byte data and string for arbitrary-length string (UTF-8) data. If you can limit the length to a certain number of bytes, always use one of bytes1 to bytes32 because they are much cheaper.

having a fixed length always saves gas. Refer to this question as well.

  1. Removing useless code as explained earlier under contract deployment will save gas even when functions are executed, if that can be done inside functions.

  2. Not using libraries when implementing the functionality is cheaper for simple usages.

Calling library for simple usages may be costly. If the functionality is simple and feasible to implement inside the contract as it avoids the step of calling the library. execution cost for the functionality only will still be the same for both.

  1. Using visibility external for the functions only accessed externally forces to use calldata as the parameter location and this saves some gas when the function executes.

  2. Using memory variables within functions locally when possible saves gas of accessing the storage.

These are some ways of saving gas and there may be many other methods depending on the requirements.

2
  • 1
    Under 1-A, if g() is expensive it should be on the rhs in both examples. The lhs will always be run, regardless. The only reason to move it to the lhs for && is if g() is more likely to return false, thus being more likely to short-circuit an &&, but not an ||.
    – Sam Bull
    Mar 16, 2018 at 15:53
  • yes, thanks for pointing the mistake g(y) should be in rhs :) Mar 16, 2018 at 17:39
10

Your solidity code will be compiled to bytecode, which consists of Ethereum Virtual Machine (EVM) instructions. The gas cost of a function call is the sum of the gas cost of all the EVM instructions that were executed. An easy to read table of the exact gas costs of each of these instructions can be found here:

If you want all the details, you should take a look at appendix H of the yellow paper:

As you can see, instructions like ADD and MUL are very cheap at only 3 and 5 gas.

Instructions like BALANCE and CREATE are much more expensive at 400 and 32000 gas.

Reading from storage costs 200 per SLOAD instruction, and writing to storage costs 5000 gas, but reading from memory and writing to memory costs only 3 gas.

Internal transactions (sending Ether) will cost you 21000 every time.

Based on this, here's some advice on reducing gas cost:

  • Only write to storage when you are finished with your computations, use local variables to remember your progress (if applicable)

  • Avoid too much writing to and reading from storage

  • Avoid creating too many new contracts

  • Avoid reading too much global state (for example getting the balance of an address)

  • Avoid too many transactions

  • Use bytes32 instead of string for small strings

  • Avoid loops that run too many times, especially if they contain expensive instructions

0
2

I believe writing the functions in smartcontract using inline assembly functions makes it way more cost efficient than any changes you try to make in your contract. a simple for loop function in solidity taking x amount of wei. where as the same function written in solidity's inline assembly takes as low as 0.7(x) amount of wei.

1
  • Interesting! Do you know of any good resources for doing this? Thanks! 🙏 Feb 1, 2021 at 4:58

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