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 #136

Open
code423n4 opened this issue Jul 19, 2022 · 0 comments
Open

Gas Optimizations #136

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

Comments

@code423n4
Copy link
Contributor

[G01] Unchecked Increments in Loops

When incrementing i in for loops there is no chance of overflow so unchecked can be used to save gas. I ran a simple test in remix and found deployment savings of 31,653 gas and on each function call saved ~141 gas per iteration.

contract Test {
	function loopTest() external {
		for (uint256 i; i < 1; ++i) {
		Deployment Cost: 125,637, Cost on function call: 24,601
		vs
		for (uint256 i; i < 1; ) {
		// for loop body
		unchecked { ++i }
		Deployment Cost: 93,984, Cost on function call: 24,460
		}
	}
}

For loops that can use unchecked increments:
BytesUtils.sol#L266
BytesUtils.sol#L313
DNSSECImpl.sol#L93
ETHRegistrarController.sol#L256
ERC1155Fuse.sol#L92
ERC1155Fuse.sol#L205

[G02] Pre Increments in Loops

In for loops pre increments can be used to save a small amount of gas per iteration.
I ran a test in remix using a for loop and found the deployment savings of 497 gas and ~5 gas per iteration.

contract Test {
	function loopTest() external {
		for (uint256 i; i < 1; i++) {
		(Deployment cost: 118,408, Cost on function call: 24,532)
		vs
		for (uint256 i; i < 1; ++i) {
		(Deployment cost: 117,911, Cost on function call: 24,527)
		}
	}
}

For loops that can use pre increments:
BytesUtils.sol#L266
BytesUtils.sol#L313
DNSSECImpl.sol#L93
ETHRegistrarController.sol#L256

[G03] Custom Errors

As your using a solidity version > 0.8.4 you can replace revert strings with custom errors. This will save in deployment costs and runtime costs.
Based on a test in remix, replacing a single revert string with a custom error saved 12,404 gas in deployment cost and 86 gas on each function call.

contract Test {
	uint256 a;
	function check() external {
		require(a != 0, "check failed");
	}
}   (Deployment cost: 114,703, Cost on Function call: 23,392)
vs 
contract Test {
	uint256 a;
	error checkFailed();
	function check() external {
		if (a != 0) revert checkFailed();
	}
}   (Deployment cost: 102,299, Cost on Function call: 23,306)

Instances where custom errors can be implemented:
RRUtils.sol#L307
ETHRegistrarController.sol#L99-L102
ETHRegistrarController.sol#L137-L139
ETHRegistrarController.sol#L196-L199
ETHRegistrarController.sol#L232-L242
ETHRegistrarController.sol#L259-L261
ReverseRegistrar.sol#L41-L54
BytesUtil.sol#L28
BytesUtil.sol#L42
ERC1155Fuse.sol#L60-L62
ERC1155Fuse.sol#L85-L87
ERC1155Fuse.sol#L107-L109
ERC1155Fuse.sol#L176-L179
ERC1155Fuse.sol#L195-L203
ERC1155Fuse.sol#L215-L217
ERC1155Fuse.sol#L248-L252
ERC1155Fuse.sol#L290-L293
Controllable.sol#L17

[G04] Long Revert Strings

If you opt not to use custom errors keeping revert strings <= 32 bytes in length will save gas.
I ran a test in remix and found the savings for a single short revert string vs long string to be 9,377 gas in deployment cost and 18 gas on function call.

contract Test {
	uint256 a;
	function check() external {
		require(a != 0, "short error message"); 
		(Deployment cost: 114,799, Cost on function call: 23,392)	
		vs 
		require(a != 0, "A longer Error Message over 32 bytes in     
        length"); 
		(Deployment cost: 124,176, Cost on function call: 23,410)	
	}
}

I recommend shortenning the following revert strings to < 32 bytes in length:
ETHRegistrarController.sol#L99-L102
ETHRegistrarController.sol#L137-L139
ETHRegistrarController.sol#L198
ETHRegistrarController.sol#L232-L242
ETHRegistrarController.sol#L259-L261
ReverseRegistrar.sol#L41-L54
ERC1155Fuse.sol#L60-L62
ERC1155Fuse.sol#L85-L87
ERC1155Fuse.sol#L107-L109
ERC1155Fuse.sol#L176-L179
ERC1155Fuse.sol#L195-L203
ERC1155Fuse.sol#L215-L217
ERC1155Fuse.sol#L248-L252
ERC1155Fuse.sol#L290-L293
Controllable.sol#L17

[G05] && in Require Statements

If optimising for runtime costs over deployment costs you can seperate && in require functions into 2 parts. I ran a basic test in remix and it cost an extra 234 gas to deploy but will save ~9 gas everytime the require function is called. (note: If you upgrade to solidity version > 0.8.13 this is no longer true)

contract Test {
	uint256 a = 0;
	uint256 b = 1;

	function test() external {
		require(a == 0 && b > a) 
		(Deployment cost: 123,291, Cost on function call: 29,371)
		vs
		require(a == 0);
		require(b > a);
		(Deployment cost: 123,525, Cost on function call: 29,362)
	}
}

Require statements that can be split up:
BytesUtils.sol#L268
ERC1155Fuse.sol#L215-L218
ERC1155Fuse.sol#L290-L293

@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
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

1 participant