Optimizing your Solidity contract’s gas usage

As everyone in the Ethereum community knows, Gas is a necessary evil for the execution of smart contracts. If you specify too little, your transaction may not get picked up for processing in a timely manner — or, die in the middle of processing a smart contract action. That being said, a smart contract should not be greedy or loose with the valuable resources that the users entrust to them. It is for this reason that we will look into tuning and optimizing our contract to minimize the amount of gas required.

Working with Gas as a User

When specifying a gas amount in Ethereum there are two components to take into consideration. The first is Gas Limit or the total gallons of gas to put in the tank of your smart contract. Note that this amount really only affects the smart contract execution itself and must be sufficient for all of the resources required by the smart contract. Otherwise, you will get an Out of Gas exception. Tools like MetaMask will attempt to make their best guess at this value and is more or less right on a non-congested ethereum network. A key point it is that any unused gas will be returned to you.

ie. You set a gas limit of 300,000 and the transaction only takes 200,000 — you won’t be charged for 300,000. The gas limit is how much you are willing to spend, but not how much you have to spend.

The second parameter related to gas consumption for sending a transaction is the Gas Price. This amount is typically denoted in gwei(10⁹ wei) and does not affect the execution of transaction or smart contract interaction, but rather the speed at which the transaction gets added to a block. To use the fuel in a car analogy, this price is the price of the fuel that you pay. If you pay a higher gas price, your transaction will get added to a block more quickly, thus speeding up the processing of your transaction. In today’s terms, 20 gwei is an average price to pay and means your transaction will get processed fairly quickly. A price of 50 gwei will get you almost immediate processing, while a price of 5 gwei will take several minutes. The gas price is a powerful feature to have at your disposal depending on the type of dApp you are trying to write. Places like https://ethgasstation.info can help you to understand the current network load and corresponding wait times related to gas price.

MetaMask Transaction(See Gas Limit and Gas Price)

Working with Gas as a Developer

As a developer, you are not worried about Gas Price at all, but you want your smart contract Gas Limits to fall within a reasonable cost basis for your particular dApp. For instance, if you are creating a smart contract to handle custody of a million dollars worth of eth, you may not be concerned with a $5 gas limit. However, if you were doing a social platform with $5 gas limits for every post you would bankrupt your users very quickly. You need to design your smart contracts to fit with your use case as processing power and storage come at a high premium in ethereum right now.

There are two main costs associated with sending and executing an Ethereum transaction on the blockchain.

  • Execution cost
  • Transaction cost

Note: Since execution cost in included in the transaction cost, we are talking about the transaction cost in most cases when referring to a transaction’s gas limit.

Execution cost is primarily associated with the storage of global variables and the processing power used for calculations and local variable manipulation. So, a simple summary would be, All costs related to the internal storage and manipulation of the contract.

Transaction cost includes the Execution cost as stated before AND the cost of sending data to the blockchain. Some of these variables include:

  • Base transaction cost(common) ~21,000
  • Contract deployment(first time) ~32,000
  • Transaction input costs(common) ~4 for 0-byte and 64 for non-0 byte inputs
  • Contract-initiated transactions cost ~32,000 each

All of these factors can cause your gas costs to add up quickly. Here are some main considerations when developing or tuning a contract:

Contract Size

The overall size of your contract will play a part in tx cost for all interactions with it. If you are able to break the contract up into smaller separate contracts, this will decrease user’s gas costs when interacting. For instance, if you have a dApp that supports car and motorcycle rentals — good design would suggest that you make a single contract and share common parts. However, if the motorcycle market is down and no-one is renting motorcycles, it does not make sense for the car rental users to have to pay for the contract bloat of unused code. It may make sense to break the contract up into an Auto and Moto contract respectively.

Global Variables(Storage)

Global variables are stored or persisted in a contract’s state on the blockchain. This is perhaps the most expensive operation in terms of gas for a transaction. It is therefore imperative to have the minimum memory footprint for your stored global variables. This is a good programming practice in all languages, but it is especially crucial in a constrained environment such as the ethereum blockchain. An instance of this would be using the expensive type string when you could use a uint to denote something.

string STATUS = ‘unknown status’; //This is really expensive
uint STATUS = 0; //This is a lot cheaper

Note: It is important to realize that a uint == uint256. Also, for whatever reason, a uint256 takes less gas to store than a uint8. So, if you are trying to optimize data types as you should, just realize this caveat.

Remove as much duplication within structs as you can. If you have member variables within structs that are storing the same value, remove the duplicates if you can without sacrificing security.

struct Game {
uint256 betAmount;
uint outcome;
uint status;
JediBet originator;
JediBet taker;
}

JediBet orig; // There is no reason to have these global vars
JediBet take; // when they are already in the Game struct

Tuning a contract

We will now take a look at our Bet.sol contract in Remix. Open up Remix in your browser, create a new contract, and paste in the code from here. Put your cursor on line 142— at the generateBetOutcome method and take a look in the upper right hand corner of the editor. You will see a gas pump icon and an estimate for the gas as shown here.

How to check estimated execution gas cost for a method

This tool is invaluable for a quick look at your estimated gas for a method invocation, but it is just an estimate. For private methods such as this, you don’t have the luxury of calling it and seeing the gas usage from the debug logs.

If you want to see a closer estimate of how much the execution will cost, you can make it public and call it directly from remix. Note that this is nowhere near the Remix IDE’s estimated cost, but it could differ based on the state of the contract at the time of execution.

Here are some costs per operation within Solidity to reference while tuning your contract(s).

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

Summary

Tuning a contract is not an exact science, but is a balance of security, good coding practices, and cost. Optimizing code for performance and security is a new concept for most developers and the devastating effects of not doing so are only now being understood. Hopefully, this article has helped you as much as it has me in understanding and optimizing gas execution and transaction cost.