Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gas Optimizations #138

Open
code423n4 opened this issue Jul 19, 2022 · 1 comment
Open

Gas Optimizations #138

code423n4 opened this issue Jul 19, 2022 · 1 comment
Labels
bug Something isn't working G (Gas Optimization)

Comments

@code423n4
Copy link
Contributor

Gas Report

Table of Contents

Array length should not be looked up in every iteration

IMPACT

It wastes gas to read an array's length in every iteration of a for loop, even if it is a memory or calldata array: 3 gas per read.

PROOF OF CONCEPT

5 instances:

DNSSECImpl.sol

RRUtils.sol

ETHRegistrarController.sol

ERC1155Fuse.sol

TOOLS USED

Manual Analysis

MITIGATION

Caching the length in a variable before the for loop

Bools for storage are expensive

IMPACT

Booleans are more expensive than uint256: each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back.
Compared to using uint256, this costs an extra 100 gas, even one Gsset 20000 gas when changing from 'false' to 'true' - if this is not the first time setting it to true.

PROOF OF CONCEPT

2 instances:

ERC1155Fuse.sol

Controllable.sol

TOOLS USED

Manual Analysis

MITIGATION

Replace bool with uint256.
Use uint256(1) and uint256(2) instead of true/false

Caching storage variables in local variables to save gas

IMPACT

Anytime you are reading from storage more than once, it is cheaper in gas cost to cache the variable: a SLOAD cost 100gas, while MLOAD and MSTORE cost 3 gas.

In particular, in for loops, when using the length of a storage array as the condition being checked after each loop, caching the array length can yield significant gas savings if the array length is high

PROOF OF CONCEPT

2 instances:

ETHRegistrarController.sol

scope: _consumeCommitment:

NameWrapper.sol

scope: setUpgradeContract:

TOOLS USED

Manual Analysis

Constants can be private

IMPACT

Marking constants as private save gas upon deployment, as the compiler does not have to create getter functions for these variables. It is worth noting that a private variable can still be read using either the verified contract source code or the bytecode.

PROOF OF CONCEPT

2 instances:

ETHRegistrarController.sol

TOOLS USED

Manual Analysis

MITIGATION

Make the constants private instead of public

  • gas cost before change:

·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 2010419 · 6.4 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·

  • gas cost after change:

·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 1974221 · 6.4 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·

This saves 36,198 gas upon deployment.

Custom Errors

IMPACT

Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained - here

Custom errors are defined using the error statement

PROOF OF CONCEPT

41 instances:

BytesUtils.sol

RRUtils.sol

ETHRegistrarController.sol

ReverseRegistrar.sol

BytesUtil.sol

ERC1155Fuse.sol

Controllable.sol

TOOLS USED

Manual Analysis

MITIGATION

Replace require and revert statements with custom errors.

For instance, in ETHRegistrarController.sol:

-require(resolver != address(0),"ETHRegistrarController: resolver is required when data is supplied")
+if (resolver == address(0)) {
+		revert ResolverRequired();
+}

and define the custom error in the contract

+error ResolverRequired();

Functions with access control cheaper if payable

PROBLEM

A function with access control marked as payable will lbe cheaper for legitimate callers: the compiler removes checks for msg.value, saving approximately 20 gas per function call.

PROOF OF CONCEPT

6 instances:

DNSSECImpl.sol

Owned.sol

ReverseRegistrar.sol

NameWrapper.sol

TOOLS USED

Manual Analysis

MITIGATION

Mark these functions as payable

Inline functions

PROBLEM

When we define internal functions to perform computation:

  • The contract’s code size gets bigger
  • the function call requires additional stack operations, making it consume more gas than if it was inlined.

When it does not affect readability, it is recommended to inline internal functions that are called only once.

PROOF OF CONCEPT

3 instances:

DNNSECImpl.sol

ETHRegistrarController.sol

ReverseRegistrar.sol

TOOLS USED

Manual Analysis

MITIGATION

Inline these functions where they are called. Another method is to use the viaIR compiler optimization flag, which helps with inline optimizations.

Mathematical optimizations

PROBLEM

X += Y costs 22 more gas than X = X + Y. This can mean a lot of gas wasted in a function call when the computation is repeated n times (loops)

PROOF OF CONCEPT

9 instances include:

RRUtils.sol

TOOLS USED

Manual Analysis

MITIGATION

use X = X + Y instead of X += Y (same with -). If Y ==1, use ++Y or --Y.

Modifier instead of duplicate require

PROBLEM

When a require statement is used multiple times, it is cheaper in deployment costs to use a modifier instead.

PROOF OF CONCEPT

4 instances include:

BytesUtils.sol

ERC1155Fuse.sol

TOOLS USED

Manual Analysis

MITIGATION

Use modifiers for these repeated statements

Prefix increments

IMPACT

Prefix increments are cheaper than postfix increments - 6 gas. This can mean interesting savings in for loops.

PROOF OF CONCEPT

5 instances:

BytesUtils.sol

DNSSECImpl.sol

ETHRegistrarController.sol

StringUtils.sol

TOOLS USED

Manual Analysis

MITIGATION

change variable++ to ++variable.

Revert strings length

IMPACT

Revert strings cost more gas to deploy if the string is larger than 32 bytes.

PROOF OF CONCEPT

22 instances:

ETHRegistrarController.sol

ReverseRegistrar.sol

ERC1155Fuse.sol

Controllable.sol

TOOLS USED

Manual Analysis

MITIGATION

Write the error strings so that they do not exceed 32 bytes. For further gas savings, consider also using - custom errors.

For instance, changing all strings in ETHRegistrarController.sol to strings shorter than 32 bytes:

  • gas cost before change:

·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 2010419 · 6.4 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·

  • gas cost after change:

·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 1932761 · 6.4 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·

This saves 77,658 gas upon deployment, an average of 11,094 gas saved per revert string.

Unchecked arithmetic

IMPACT

The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.

if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked block will save gas

PROOF OF CONCEPT

9 instances:

BytesUtils.sol

DNSSECImpl.sol

ETHRegistrarController.sol

StringUtils.sol

ERC1155Fuse.sol

TOOLS USED

Manual Analysis

MITIGATION

Place the arithmetic operations in an unchecked block.

For instance, doing it for the 3 instances mentioned in ETHRegistrarController:

  • gas cost before changes:

·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Methods │
································|·····················|·············|·············|···············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · eur (avg) │
································|·····················|·············|·············|···············|···············|··············
| ETHRegistrarController · register · 233372 · 377120 · 293714 · 15 · - │
································|·····················|·············|·············|···············|···············|··············
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 2010419 · 6.7 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·

  • gas cost after changes:

·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Methods │
································|·····················|·············|·············|···············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · eur (avg) │
································|·····················|·············|·············|···············|···············|··············
| ETHRegistrarController · register · 233244 · 376921 · 293558 · 15 · - │
································|·····················|·············|·············|···············|···············|··············
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 1997901 · 6.7 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·

It saves 12,518 upon deployment, and an average of 156 gas per register call - here for data.length == 15, so an average of 10 gas per byte of data.

Upgrade Solidity compiler version

IMPACT

  • 0.8.10 removes contract existence checks if the external call has a return value - 700 gas

PROOF OF CONCEPT

All the contracts in scope have the pragma set to ^0.8.4

TOOLS USED

Manual Analysis

MITIGATION

Upgrade these contracts compiler versions.

use Assembly for simple setters

IMPACT

Where it does not affect readability, using assembly allows to save gas not only on deployment, but also on function calls.
This is the case for instance for simple admin setters.

PROOF OF CONCEPT

2 instances:

Owned.sol

NameWrapper.sol

MITIGATION

Use assembly to write the new value in storage. For instance in NameWrapper.sol:

-  metadataService = _newMetadataService;
+  assembly {
+    sstore(metadataService.slot, _newMetadataService)
+  }
  • gas cost before changes:

·---------------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
··························································|···························|···············|······························
| Methods │
································|·························|·············|·············|···············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · eur (avg) │
································|·························|·············|·············|···············|···············|··············
| NameWrapper · setMetadataService · - · - · 29013 · 1 · - │
································|·························|·············|·············|···············|···············|··············
| Deployments · · % of limit · │
··························································|·············|·············|···············|···············|··············
| NameWrapper · - · - · 5250093 · 17.5 % · - │
·---------------------------------------------------------|-------------|-------------|---------------|---------------|-------------·

  • gas cost after changes:

·---------------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
··························································|···························|···············|······························
| Methods │
································|·························|·············|·············|···············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · eur (avg) │
································|·························|·············|·············|···············|···············|··············
| NameWrapper · setMetadataService · - · - · 28962 · 1 · - │
································|·························|·············|·············|···············|···············|··············
| Deployments · · % of limit · │
··························································|·············|·············|···············|···············|··············
| NameWrapper · - · - · 5238900 · 17.5 % · - │
·---------------------------------------------------------|-------------|-------------|---------------|---------------|-------------·

It saves 11,193 gas upon deployment, and an average of 51 gas per function call.

@code423n4 code423n4 added bug Something isn't working G (Gas Optimization) labels Jul 19, 2022
code423n4 added a commit that referenced this issue Jul 19, 2022
@jefflau
Copy link
Collaborator

jefflau commented Aug 1, 2022

High quality submission

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working G (Gas Optimization)
Projects
None yet
Development

No branches or pull requests

2 participants