How to reduce gas cost in Solidity

tak
LayerX
Published in
6 min readFeb 18, 2019

Gas efficiency tend to be low prioritized in most of the case. Since many projects pay attentions on security or cording readability. This time, I focus on gas cost saving.

Then, I introduce 13 tips to reduce gas cost.

Pre conditions

1. Avoid unnecessary event emission

Basically, logging opcodes are costly and gas cost increase when its argument increase. If only one argument are taken, the gas cost is 750. If two arguments are taken, the gas cost is 1125. Additionally, other factors lead to additional charges, like index value or data size.

Let’s compare gas cost between two function, one function have event and the other don’t have event.

// 318 gas
function noEvent(uint256 a, uint256 b) public returns (uint256 c) {
c = a + b;
}
// 1379 gas
event Result(uint256 c);
function hasEvent(uint256 a, uint256 b) public returns (uint256 c) {
c = a + b;
emit Result(c);
}

The event having function gas cost is 4 time larger than the other.

2. Convert byte[] to bytesX

The solidity official site state as follow.

You should use bytesX over byte[], because it is cheaper, since byte[] adds 31 padding bytes between the elements. Always use one of the value types bytes1 to bytes32 because they are much cheaper.

3. Change string to byteX if possible

The string type is basically equal to bytes so that we can use bytesX as alternative of string. It’s better to covert string to bytesX from efficiency perspective, as long as the string length is less than 32.

Please see the bellow comparison. When we convert the string “hello world!” to bytes, the gas cost becomes nearly 3 times chipper than using string.

// 599 gas
function useString() public returns(string memory a) {
a = "hello world!";
}
// 196 gas
function useByte() public returns(bytes32 a) {
a = bytes32("hello world!");
}

4. Pack struct tightly

The solidity official site state as follows.

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

Ensure that you try to order your storage variables and struct members such that they can be packed tightly.

I confirmed this statement.Please see bellow. The unpacked case is nearly 30,000 higher than the packed case.

// 112465 gas
struct UserUnpacked {
bool isMarried;
bytes32 description;
bytes23 name;
bytes10 picture;
bytes20 location;
uint64 id;
uint8 age;
}
UserUnpacked public userUnpacked;function unpackedStruct(
uint64 id, bytes23 name, bytes32 description, bytes10
picture, bool isMarried, bytes20 location, uint8 age) public
{
userUnpacked = UserUnpacked(
isMarried, description, name, picture, location, id, age);
}
------------------------------------------------------------------// 82565 gas
struct UserPacked {
uint64 id;
uint8 age;
bytes23 name;
bytes32 description;
bytes20 location;
bytes10 picture;
bool isMarried;
}
UserPacked public userPacked;function packedStruct(
uint64 id, bytes23 name, bytes32 description, bytes10
picture, bool isMarried, bytes20 location, uint8 age) public
{
userPacked = UserPacked(
id, age, name, description, location, picture, isMarried);
}

5. Assert is big eater

The assert-style exceptions consume all gas available to the call. Please see the bellow simple example. The execution cost is 2978472!, when the assertion error happens and the gas limit is 3,000,000.

On the other hand, the require-style exceptions will not consume any gas starting from the Metropolis release. The execution cost of bellow example is only 382.

// 2978472 gas (gas limit is 3000000)
function isAssert(uint256 a, uint256 b) public returns(uint256 c) {
assert(a + b != 0);
c = a + b;
}
// 382 gas
function isRequire(uint256 a, uint256 b) public returns(uint256 c) {
require(a + b != 0);
c = a + b;
}

6. Function order affect gas consumption

The order of function will also have an impact on gas consumption. Because in smart contracts, there is a difference in the order of the functions. Each position will have an extra 22 gas. The order is depend on method ID, So if you rename the frequently accessed function to more early method ID, you can save gas cost.
Please see the bellow site for further information.

[Solidity] How does function name affect gas consumption in smart contract

7. Internal Function Calls is efficient

The solidity official site state as follows.

These function calls are translated into simple jumps inside the EVM. Just passing memory references to internally-called functions is very efficient.

So, moving some external call to internal improve efficiency.

8. Activate the optimizer

The solidity official site state as follows.

Before you deploy your contract, activate the optimizer when compiling using “solc — optimize — bin sourceFile.sol”

If you want the initial contract deployment to be cheaper and the later function executions to be more expensive, set it to “ — optimize-runs=1”
if you expect many transactions and do not care for higher deployment cost and output size, set “— optimize-runs” to a high number.

9. Not use library always

Not using libraries when implementing the functionality is cheaper for simple usages. Calling library for a simple usages may be costly.
Please see the bellow site for further information.

How to write an optimized (gas-cost) smart contract?

10. Compress input in smart contract

The basic gas for the transaction sending is 21,000. The gas increase when the data size increase. Its rate is 68 gas per byte, and 4 gas if the byte is 0x00. So, you can reduce gas cost by compressing input data.

Please see this site for further information. This is quite interesting technique. If efficiency is critical, it’s worth to do.

[Solidity] Compress input in smart contract

11. Short-circuiting rules

The solidity official site state as follows.

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 setting less costly function to “f(x)” and setting costly function to “g(x)” is efficient.

12. Optimize code by assembly

The solidity official site state as follows

Inline assembly is also beneficial in cases where the optimizer fails to produce efficient code.

13. External function is efficient if argument is large

Even though, the Solidity official site state as follows, I can not confirm this.

External functions are sometimes more efficient when they receive large arrays of data.

When I pass 320 byte of data, the gas cost was exact same between public function and external function.

// 618 gas
function publicAccess(
bytes32 a, bytes32 b, bytes32 c, bytes32 d, bytes32 e,
bytes32 f, bytes32 g, bytes32 h, bytes32 i, bytes32 j
)
public returns(uint256 l)
{
l = a.length + b.length + c.length + d.length + e.length;
l += f.length + g.length + h.length + i.length + j.length;
}
// 618 gas
function externalAccess(
bytes32 a, bytes32 b, bytes32 c, bytes32 d, bytes32 e,
bytes32 f, bytes32 g, bytes32 h, bytes32 i, bytes32 j
)
external returns(uint256 l)
{
l = a.length + b.length + c.length + d.length + e.length;
l += f.length + g.length + h.length + i.length + j.length;
}
Click to read today’s top story

--

--

tak
LayerX
Writer for

Japanese software engineer. Like blockchain, sauna and manga. re795h@gmail.com