From 3afa909b42aae14b9150c54319f7fe02d5762577 Mon Sep 17 00:00:00 2001 From: Richie Date: Mon, 13 Dec 2021 16:22:50 -0800 Subject: [PATCH 01/21] =?UTF-8?q?=E2=9C=A8=20Add=20a=20few=20more=20tips?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e210c47..b589d1f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A list of common Solidity optimization tips and myths. ## Tips -### Right Shift Instead of Dividing By 2 +### When dividing by two, use `>> 1` instead of `/ 2` The `SHR` opcode is 3 gas cheaper than `DIV` and also bypasses Solidity's division by 0 prevention overhead. @@ -12,10 +12,63 @@ The `SHR` opcode is 3 gas cheaper than `DIV` and also bypasses Solidity's divisi ```solidity // Unoptimized: -uint256 two = 4 / 2; +uint256 foo = bar / 2; // Optimized: -uint256 two = 4 >> 1; +uint256 foo = bar >> 1; ``` + +### Use `>=` and `<=` instead of `>` and `<` + +Explanation tbd + +- [Gas Usage]() +- [Full Example]() + +```solidity +// Unoptimized: +bool foo = bar > 0; + +// Optimized: +bool foo = bar >= 1; +``` + + +### Using `!=` is usually cheaper than `>` or `<` + +Explanation tbd + +- [Gas Usage]() +- [Full Example]() + +```solidity +// Unoptimized: +uint256 foo = bar < 32; + +// Optimized: +uint256 foo = bar != 32; +``` + + +### Use `
.code.length` instead of assembly `extcodesize` to avoid variable overhead + +Solidity [0.8.1](https://github.com/ethereum/solidity/blob/develop/Changelog.md#081-2021-01-27) implements a shortcut for `
.code.length` that avoids copying code to memory. More explanation TBD + +- [Gas Usage]() +- [Full Example]() + +```solidity +// Unoptimized: +uint256 foo; +assembly { + foo := extcodesize(bar) +} +return foo; + +// Optimized: +return bar.code.length; +``` + + ## Myths From 0d796cb12f895988fa47528757a366c4c83278f9 Mon Sep 17 00:00:00 2001 From: Richie Date: Wed, 15 Dec 2021 00:25:49 -0800 Subject: [PATCH 02/21] docs: update descriptions and formatting --- README.md | 88 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index b589d1f..92d2330 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,84 @@ # golf-course A list of common Solidity optimization tips and myths. -## Tips +# Tips -### When dividing by two, use `>> 1` instead of `/ 2` - -The `SHR` opcode is 3 gas cheaper than `DIV` and also bypasses Solidity's division by 0 prevention overhead. - -- [Gas Usage]() -- [Full Example]() +- - - - +### 1. When dividing by two, use `>> 1` instead of `/ 2` ### ```solidity -// Unoptimized: -uint256 foo = bar / 2; +/// 🤦 Unoptimized: +uint256 two = 4 / 2; -// Optimized: -uint256 foo = bar >> 1; +/// 🚀 Optimized: +uint256 two = 4 >> 1; ``` +The `SHR` opcode is 3 gas cheaper than `DIV` and also bypasses Solidity's division by 0 prevention overhead. + - [Gas Usage]() + - [Full Example]() -### Use `>=` and `<=` instead of `>` and `<` - -Explanation tbd -- [Gas Usage]() -- [Full Example]() +- - - - +### 2. Use `>=` and `<=` instead of `>` and `<` ### ```solidity -// Unoptimized: -bool foo = bar > 0; - -// Optimized: -bool foo = bar >= 1; +/// 🤦 Unoptimized: +if (someInteger > 0) { + emit GreaterThanZero() +}; + +/// 🚀 Optimized: +if (someInteger >= 1) { + emit GreaterThanZero() +}; ``` - -### Using `!=` is usually cheaper than `>` or `<` - -Explanation tbd +`if (x >= y) {}` is algebraically equivalent to !(x < y) which gets compiled to `LT .. JUMPI` which is more efficient than `x > y` which contains an extra `ISZERO` - [Gas Usage]() - [Full Example]() +- - - - + +### 3. Using `!=` is usually cheaper than `>` or `<` ### ```solidity -// Unoptimized: -uint256 foo = bar < 32; +/// 🤦 Unoptimized: +if (bytesCount < 32) { + emit NotThirtyTwo(); +} -// Optimized: -uint256 foo = bar != 32; +/// 🚀 Optimized: +if (bytesCount != 32) { + emit NotThirtyTwo(); +} ``` +Similar to `>=` and `<=` - the `!=` gets compiled without an extra ISZERO in many cases (One notable exception being `x != 0` gets compiled the same as `x > 0`) +- [Gas Usage]() +- [Full Example]() -### Use `
.code.length` instead of assembly `extcodesize` to avoid variable overhead -Solidity [0.8.1](https://github.com/ethereum/solidity/blob/develop/Changelog.md#081-2021-01-27) implements a shortcut for `
.code.length` that avoids copying code to memory. More explanation TBD -- [Gas Usage]() -- [Full Example]() +- - - - +### 4. Use `
.code.length` instead of assembly `extcodesize` to avoid variable overhead ### ```solidity -// Unoptimized: -uint256 foo; +/// 🤦 Unoptimized: +uint256 size; assembly { - foo := extcodesize(bar) + size := extcodesize(someAddress) } -return foo; +return size; -// Optimized: +/// 🚀 Optimized: return bar.code.length; ``` +Solidity [0.8.1](https:///github.com/ethereum/solidity/blob/develop/Changelog.md#081-2021-01-27) implemented `
.code.length` so the assembly is no longer needed + +- [Gas Usage]() +- [Full Example]() +- - - - -## Myths +# Myths From 5b1cad012a7bb839fde9ac910d5f135cec850d6e Mon Sep 17 00:00:00 2001 From: Richie Date: Sat, 18 Dec 2021 10:38:58 -0800 Subject: [PATCH 03/21] dapp init GolfCourse --- .gitattributes | 1 + .gitignore | 1 + Makefile | 4 ++++ src/GolfCourse.sol | 5 +++++ src/GolfCourse.t.sol | 22 ++++++++++++++++++++++ 5 files changed, 33 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 src/GolfCourse.sol create mode 100644 src/GolfCourse.t.sol diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..52031de --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sol linguist-language=Solidity diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2e7327 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/out diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..60dd284 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +all :; dapp build +clean :; dapp clean +test :; dapp test +deploy :; dapp create GolfCourse diff --git a/src/GolfCourse.sol b/src/GolfCourse.sol new file mode 100644 index 0000000..93aa2d4 --- /dev/null +++ b/src/GolfCourse.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.6; + +contract GolfCourse { +} diff --git a/src/GolfCourse.t.sol b/src/GolfCourse.t.sol new file mode 100644 index 0000000..0b46984 --- /dev/null +++ b/src/GolfCourse.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.6; + +import "ds-test/test.sol"; + +import "./GolfCourse.sol"; + +contract GolfCourseTest is DSTest { + GolfCourse course; + + function setUp() public { + course = new GolfCourse(); + } + + function testFail_basic_sanity() public { + assertTrue(false); + } + + function test_basic_sanity() public { + assertTrue(true); + } +} From 244182ebe059cc02bc7b276cea94ae539b0cfb93 Mon Sep 17 00:00:00 2001 From: Richie Date: Sat, 18 Dec 2021 10:39:00 -0800 Subject: [PATCH 04/21] dapp install ds-test --- .gitmodules | 3 +++ lib/ds-test | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 lib/ds-test diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e124719 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/ds-test"] + path = lib/ds-test + url = https://github.com/dapphub/ds-test diff --git a/lib/ds-test b/lib/ds-test new file mode 160000 index 0000000..0a5da56 --- /dev/null +++ b/lib/ds-test @@ -0,0 +1 @@ +Subproject commit 0a5da56b0d65960e6a994d2ec8245e6edd38c248 From fc1882bacfec50787d9e9435d59fed4a9091fb21 Mon Sep 17 00:00:00 2001 From: Richie Date: Sat, 18 Dec 2021 11:25:26 -0800 Subject: [PATCH 05/21] feat: adds examples and tests --- src/GolfCourse.sol | 61 ++++++++++++++++++++++++++++++++++++++++++++ src/GolfCourse.t.sol | 30 +++++++++++++++++++--- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/GolfCourse.sol b/src/GolfCourse.sol index 93aa2d4..02fd0c8 100644 --- a/src/GolfCourse.sol +++ b/src/GolfCourse.sol @@ -2,4 +2,65 @@ pragma solidity ^0.8.6; contract GolfCourse { + function unoptimizedDivideByTwo() external pure returns (uint256 two) { + /// 🤦 Unoptimized + two = 4 / 2; + } + + function optimizedDivideByTwo() external pure returns (uint256 two) { + /// 🚀 Optimized + two = 4 >> 1; + } + + function unoptimizedPreferGteLteOverGtLt() + external + pure + returns (bool positive) + { + /// 🤦 Unoptimized + positive = 1 > 0; + } + + function optimizedPreferGteLteOverGtLt() + external + pure + returns (bool positive) + { + /// 🚀 Optimized + positive = 1 >= 1; + } + + function unoptimizedUseCodeLength() external view returns (uint256) { + /// 🤦 Unoptimized + address address_ = address(this); + uint256 size; + assembly { + size := extcodesize(address_) + } + return size; + } + + function optimizedUseCodeLength() external view returns (uint256) { + /// 🚀 Optimized + return address(this).code.length; + } + + function unoptimizedPreferNotEqualOverGtLt() + external + pure + returns (bool notThirtyTwo) + { + /// 🤦 Unoptimized + notThirtyTwo = 31 < 32; + } + + function optimizedPreferNotEqualOverGtLt() + external + pure + returns (bool notThirtyTwo) + { + /// 🚀 Optimized + notThirtyTwo = 31 != 32; + } + } diff --git a/src/GolfCourse.t.sol b/src/GolfCourse.t.sol index 0b46984..46da7b4 100644 --- a/src/GolfCourse.t.sol +++ b/src/GolfCourse.t.sol @@ -12,11 +12,33 @@ contract GolfCourseTest is DSTest { course = new GolfCourse(); } - function testFail_basic_sanity() public { - assertTrue(false); + function testUnoptimizedDivideByTwo() public { + /// 🤦 Unoptimized + assertEq(course.unoptimizedDivideByTwo(), 2); + } + function testOptimizedDivideByTwo() public { + assertEq(course.optimizedDivideByTwo(), 2); + } + function testOptimizedPreferGteLteOverGtLt() public { + assertTrue(course.optimizedPreferGteLteOverGtLt()); + } + + function testUnoptimizedPreferGteLteOverGtLt() public { + assertTrue(course.unoptimizedPreferGteLteOverGtLt()); + } + function testOptimizedUseCodeLength() public { + assertTrue(course.optimizedUseCodeLength() > 0); } - function test_basic_sanity() public { - assertTrue(true); + function testUnoptimizedUseCodeLength() public { + assertTrue(course.unoptimizedUseCodeLength() > 0); } + function testOptimizedPreferNotEqualOverGtLt() public { + assertTrue(course.optimizedPreferNotEqualOverGtLt()); + } + + function testUnoptimizedPreferNotEqualOverGtLt() public { + assertTrue(course.unoptimizedPreferNotEqualOverGtLt()); + } + } From 889b45f86ea820e769bb4052c6a483451f9afe63 Mon Sep 17 00:00:00 2001 From: Richie Date: Sat, 18 Dec 2021 11:35:54 -0800 Subject: [PATCH 06/21] docs: update readme.md with links and gas --- README.md | 47 +++++++++++++++++++------------------------- src/GolfCourse.t.sol | 22 +++++++++++---------- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 92d2330..47f0954 100644 --- a/README.md +++ b/README.md @@ -7,56 +7,48 @@ A list of common Solidity optimization tips and myths. ### 1. When dividing by two, use `>> 1` instead of `/ 2` ### ```solidity -/// 🤦 Unoptimized: +/// 🤦 Unoptimized (gas: 1401) uint256 two = 4 / 2; -/// 🚀 Optimized: +/// 🚀 Optimized (gas: 1372) uint256 two = 4 >> 1; ``` The `SHR` opcode is 3 gas cheaper than `DIV` and also bypasses Solidity's division by 0 prevention overhead. - [Gas Usage]() - - [Full Example]() + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/GolfCourse.t.sol#L15) - - - - ### 2. Use `>=` and `<=` instead of `>` and `<` ### ```solidity -/// 🤦 Unoptimized: -if (someInteger > 0) { - emit GreaterThanZero() -}; - -/// 🚀 Optimized: -if (someInteger >= 1) { - emit GreaterThanZero() -}; +/// 🤦 Unoptimized (gas: 1401) +bool positive = 1 > 0; + +/// 🚀 Optimized (gas: 1337) +bool positive = 1 >= 1; ``` `if (x >= y) {}` is algebraically equivalent to !(x < y) which gets compiled to `LT .. JUMPI` which is more efficient than `x > y` which contains an extra `ISZERO` - [Gas Usage]() -- [Full Example]() +- [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/GolfCourse.t.sol#L23) - - - - ### 3. Using `!=` is usually cheaper than `>` or `<` ### ```solidity -/// 🤦 Unoptimized: -if (bytesCount < 32) { - emit NotThirtyTwo(); -} +/// 🤦 Unoptimized (gas: 1423) +notThirtyTwo = 31 < 32; -/// 🚀 Optimized: -if (bytesCount != 32) { - emit NotThirtyTwo(); -} +/// 🚀 Optimized (gas: 1426) +notThirtyTwo = 31 != 32; ``` Similar to `>=` and `<=` - the `!=` gets compiled without an extra ISZERO in many cases (One notable exception being `x != 0` gets compiled the same as `x > 0`) - [Gas Usage]() -- [Full Example]() +- [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/GolfCourse.t.sol#L31) @@ -64,20 +56,21 @@ Similar to `>=` and `<=` - the `!=` gets compiled without an extra ISZERO in man ### 4. Use `
.code.length` instead of assembly `extcodesize` to avoid variable overhead ### ```solidity -/// 🤦 Unoptimized: +/// 🤦 Unoptimized (gas: 1535) uint256 size; +address address_ = address(this); assembly { - size := extcodesize(someAddress) + size := extcodesize(address_) } return size; -/// 🚀 Optimized: -return bar.code.length; +/// 🚀 Optimized (gas: 1582) +return address(this).code.length; ``` Solidity [0.8.1](https:///github.com/ethereum/solidity/blob/develop/Changelog.md#081-2021-01-27) implemented `
.code.length` so the assembly is no longer needed - [Gas Usage]() -- [Full Example]() +- [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/GolfCourse.t.sol#L39) - - - - diff --git a/src/GolfCourse.t.sol b/src/GolfCourse.t.sol index 46da7b4..4602a5b 100644 --- a/src/GolfCourse.t.sol +++ b/src/GolfCourse.t.sol @@ -13,32 +13,34 @@ contract GolfCourseTest is DSTest { } function testUnoptimizedDivideByTwo() public { - /// 🤦 Unoptimized assertEq(course.unoptimizedDivideByTwo(), 2); } + function testOptimizedDivideByTwo() public { assertEq(course.optimizedDivideByTwo(), 2); } - function testOptimizedPreferGteLteOverGtLt() public { - assertTrue(course.optimizedPreferGteLteOverGtLt()); - } function testUnoptimizedPreferGteLteOverGtLt() public { assertTrue(course.unoptimizedPreferGteLteOverGtLt()); } - function testOptimizedUseCodeLength() public { - assertTrue(course.optimizedUseCodeLength() > 0); + + function testOptimizedPreferGteLteOverGtLt() public { + assertTrue(course.optimizedPreferGteLteOverGtLt()); } - function testUnoptimizedUseCodeLength() public { - assertTrue(course.unoptimizedUseCodeLength() > 0); + function testUnoptimizedPreferNotEqualOverGtLt() public { + assertTrue(course.unoptimizedPreferNotEqualOverGtLt()); } + function testOptimizedPreferNotEqualOverGtLt() public { assertTrue(course.optimizedPreferNotEqualOverGtLt()); } - function testUnoptimizedPreferNotEqualOverGtLt() public { - assertTrue(course.unoptimizedPreferNotEqualOverGtLt()); + function testUnoptimizedUseCodeLength() public { + assertTrue(course.unoptimizedUseCodeLength() > 0); } + function testOptimizedUseCodeLength() public { + assertTrue(course.optimizedUseCodeLength() > 0); + } } From fb1f401ee79b1be836f565bee1b024b4d02f5e42 Mon Sep 17 00:00:00 2001 From: Richie Date: Sun, 13 Feb 2022 14:12:18 -0800 Subject: [PATCH 07/21] feat: work on formatting and add a bunch of examples --- .dapprc | 14 ++ README.md | 129 ++++++++++++------ src/GolfCourse.sol | 66 --------- src/GolfCourse.t.sol | 46 ------- src/contracts/OptimizedCacheArrLength.sol | 12 ++ src/contracts/OptimizedDivideByTwo.sol | 10 ++ src/contracts/OptimizedPlusPlusIndex.sol | 12 ++ src/contracts/OptimizedReentrancy.sol | 23 ++++ src/contracts/OptimizedUncheckedIncrement.sol | 17 +++ src/contracts/OptimizedUseImmutable.sol | 16 +++ src/contracts/UnoptimizedCacheArrLength.sol | 11 ++ src/contracts/UnoptimizedDivideByTwo.sol | 11 ++ src/contracts/UnoptimizedPlusPlusIndex.sol | 12 ++ src/contracts/UnoptimizedReentrancy.sol | 25 ++++ .../UnoptimizedUncheckedIncrement.sol | 12 ++ src/contracts/UnoptimizedUseImmutable.sol | 16 +++ src/tests/Optimized.t.sol | 55 ++++++++ src/tests/Unoptimized.t.sol | 55 ++++++++ 18 files changed, 390 insertions(+), 152 deletions(-) create mode 100644 .dapprc delete mode 100644 src/GolfCourse.sol delete mode 100644 src/GolfCourse.t.sol create mode 100644 src/contracts/OptimizedCacheArrLength.sol create mode 100644 src/contracts/OptimizedDivideByTwo.sol create mode 100644 src/contracts/OptimizedPlusPlusIndex.sol create mode 100644 src/contracts/OptimizedReentrancy.sol create mode 100644 src/contracts/OptimizedUncheckedIncrement.sol create mode 100644 src/contracts/OptimizedUseImmutable.sol create mode 100644 src/contracts/UnoptimizedCacheArrLength.sol create mode 100644 src/contracts/UnoptimizedDivideByTwo.sol create mode 100644 src/contracts/UnoptimizedPlusPlusIndex.sol create mode 100644 src/contracts/UnoptimizedReentrancy.sol create mode 100644 src/contracts/UnoptimizedUncheckedIncrement.sol create mode 100644 src/contracts/UnoptimizedUseImmutable.sol create mode 100644 src/tests/Optimized.t.sol create mode 100644 src/tests/Unoptimized.t.sol diff --git a/.dapprc b/.dapprc new file mode 100644 index 0000000..009c5e3 --- /dev/null +++ b/.dapprc @@ -0,0 +1,14 @@ +# Basic build/test configuration. +export DAPP_SOLC_VERSION=0.8.11 +export DAPP_BUILD_OPTIMIZE=1 +export DAPP_BUILD_OPTIMIZE_RUNS=1000000 +export DAPP_LINK_TEST_LIBRARIES=0 +export DAPP_TEST_VERBOSITY=1 +export DAPP_TEST_SMTTIMEOUT=500000 + +if [ "$DEEP_FUZZ" = "true" ] +then + export DAPP_TEST_FUZZ_RUNS=10000 # Fuzz for a long time if DEEP_FUZZ is set to true. +else + export DAPP_TEST_FUZZ_RUNS=100 # Only fuzz briefly if DEEP_FUZZ is not set to true. +fi diff --git a/README.md b/README.md index 47f0954..d750ed0 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,126 @@ # golf-course A list of common Solidity optimization tips and myths. + # Tips - - - - -### 1. When dividing by two, use `>> 1` instead of `/ 2` ### +### Use UINT instead of BOOL for reentrancy guard ### ```solidity -/// 🤦 Unoptimized (gas: 1401) -uint256 two = 4 / 2; +/// 🤦 Unoptimized (gas: 22180) + +bool private locked = false; +modifier nonReentrant() { + require(locked == 1, "REENTRANCY"); + locked = 2; + _; + locked = 1; +} -/// 🚀 Optimized (gas: 1372) -uint256 two = 4 >> 1; +/// 🚀 Optimized (gas: 2053) +bool private locked = 1; +modifier nonReentrant() { + require(locked == 1, "REENTRANCY"); + locked = 2; + _; + locked = 1; +} ``` -The `SHR` opcode is 3 gas cheaper than `DIV` and also bypasses Solidity's division by 0 prevention overhead. - - [Gas Usage]() - - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/GolfCourse.t.sol#L15) - +Use a reentrancy guard like [Solmate](https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol) which employs uint instead of boolean storage variable which saves gas. + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedReentrancy.sol) - - - - +### When iterating through a storage array, cache the array length first ### -### 2. Use `>=` and `<=` instead of `>` and `<` ### ```solidity -/// 🤦 Unoptimized (gas: 1401) -bool positive = 1 > 0; +uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; -/// 🚀 Optimized (gas: 1337) -bool positive = 1 >= 1; +/// 🤦 Unoptimized (gas: 3077) +for (uint256 index; index < arr.length; ++index) {} + +/// 🚀 Optimized (gas: 2085) +uint256 arrLength = arr.length; +for (uint256 index; index < arrLength; ++index) {} ``` +Caching the array length first saves an SLOAD on each iteration of the loop. + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedCacheArrLength.sol) + +- - - - +### Use unchecked with counter incrementing logic ### -`if (x >= y) {}` is algebraically equivalent to !(x < y) which gets compiled to `LT .. JUMPI` which is more efficient than `x > y` which contains an extra `ISZERO` +```solidity + +/// 🤦 Unoptimized (gas: 1990) +for (uint256 index; index < arrLength; ++index) {} + + +/// 🚀 Optimized (gas: 1315) +function _uncheckedIncrement(uint256 counter) private pure returns(uint256) { + unchecked { + return counter + 1; + } +} +... +for (uint256 index; index < arrLength; index = _uncheckedIncrement(index)) {} +``` +It is a logical impossibility for index to overflow if it is always less than another integer (index < arrLength). Skipping the unchecked saves ~60 gas per iteration. Note: Part of the savings here comes from the fact that as of Solidity 0.8.2, the compiler will inline this function automatically. Using an older pragma would reduce the gas savings. For additional info see [hrkrshnn's writeup](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedUncheckedIncrement.sol) -- [Gas Usage]() -- [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/GolfCourse.t.sol#L23) - - - - +### Use ++index instead of index++ to increment a loop counter ### -### 3. Using `!=` is usually cheaper than `>` or `<` ### ```solidity -/// 🤦 Unoptimized (gas: 1423) -notThirtyTwo = 31 < 32; +/// 🤦 Unoptimized (gas: 2064) +for (uint256 index; index < arrLength; index++) {} -/// 🚀 Optimized (gas: 1426) -notThirtyTwo = 31 != 32; +/// 🚀 Optimized (gas: 2014) +for (uint256 index; index < arrLength; ++index) {} ``` -Similar to `>=` and `<=` - the `!=` gets compiled without an extra ISZERO in many cases (One notable exception being `x != 0` gets compiled the same as `x > 0`) - -- [Gas Usage]() -- [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/GolfCourse.t.sol#L31) +Due to reduced stack operations, using ++index saves 5 gas per iteration + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedPlusPlusIndex.sol) +- - - - +### Prefer using immutable to storage ### +```solidity +uint256 public immutableNumber; +uint256 public storageNumber; + +/// 🤦 Unoptimized (gas: 1042) +uint256 sum = storageNumber + 1; + } +/// 🚀 Optimized (gas: 1024) +uint256 sum = immutableNumber + 1; +``` +Each storage read of the state variable is replaced by the instruction push32 value, where value is set during contract construction time. For additional info see [hrkrshnn's writeup](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedUseImmutable.sol) - - - - +### When dividing by two, use `>> 1` instead of `/ 2` ### -### 4. Use `
.code.length` instead of assembly `extcodesize` to avoid variable overhead ### ```solidity -/// 🤦 Unoptimized (gas: 1535) -uint256 size; -address address_ = address(this); -assembly { - size := extcodesize(address_) -} -return size; +uint256 four = 4; +uint256 two; + +/// 🤦 Unoptimized (gas: 942) +two = four / 2; -/// 🚀 Optimized (gas: 1582) -return address(this).code.length; +/// 🚀 Optimized (gas: 866) +two = four >> 1; ``` -Solidity [0.8.1](https:///github.com/ethereum/solidity/blob/develop/Changelog.md#081-2021-01-27) implemented `
.code.length` so the assembly is no longer needed -- [Gas Usage]() -- [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/GolfCourse.t.sol#L39) +The `SHR` opcode is 3 gas cheaper than `DIV` and more imporantly, bypasses Solidity's division by 0 prevention overhead. This can be used not only when dividing by two, but with any exponent of 2. + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedDivideByTwo.sol) + + -- - - - # Myths +- - - - + +- - - - +### Note on gas numbers: The gas usage numbers include the total gas reported by Dapp Test for running a test which calls the example function. This includes the gas overhead of the test itself. The important number here is the gas savings which is the difference between optimized and unoptimized gas usage numbers. We have done our best to reduce noise in the estimates and confirm the actual gas usage based on opcodes run. But there have still been some inconsistencies in the gas numbers between the two contracts used for testing. + diff --git a/src/GolfCourse.sol b/src/GolfCourse.sol deleted file mode 100644 index 02fd0c8..0000000 --- a/src/GolfCourse.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.6; - -contract GolfCourse { - function unoptimizedDivideByTwo() external pure returns (uint256 two) { - /// 🤦 Unoptimized - two = 4 / 2; - } - - function optimizedDivideByTwo() external pure returns (uint256 two) { - /// 🚀 Optimized - two = 4 >> 1; - } - - function unoptimizedPreferGteLteOverGtLt() - external - pure - returns (bool positive) - { - /// 🤦 Unoptimized - positive = 1 > 0; - } - - function optimizedPreferGteLteOverGtLt() - external - pure - returns (bool positive) - { - /// 🚀 Optimized - positive = 1 >= 1; - } - - function unoptimizedUseCodeLength() external view returns (uint256) { - /// 🤦 Unoptimized - address address_ = address(this); - uint256 size; - assembly { - size := extcodesize(address_) - } - return size; - } - - function optimizedUseCodeLength() external view returns (uint256) { - /// 🚀 Optimized - return address(this).code.length; - } - - function unoptimizedPreferNotEqualOverGtLt() - external - pure - returns (bool notThirtyTwo) - { - /// 🤦 Unoptimized - notThirtyTwo = 31 < 32; - } - - function optimizedPreferNotEqualOverGtLt() - external - pure - returns (bool notThirtyTwo) - { - /// 🚀 Optimized - notThirtyTwo = 31 != 32; - } - -} diff --git a/src/GolfCourse.t.sol b/src/GolfCourse.t.sol deleted file mode 100644 index 4602a5b..0000000 --- a/src/GolfCourse.t.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.6; - -import "ds-test/test.sol"; - -import "./GolfCourse.sol"; - -contract GolfCourseTest is DSTest { - GolfCourse course; - - function setUp() public { - course = new GolfCourse(); - } - - function testUnoptimizedDivideByTwo() public { - assertEq(course.unoptimizedDivideByTwo(), 2); - } - - function testOptimizedDivideByTwo() public { - assertEq(course.optimizedDivideByTwo(), 2); - } - - function testUnoptimizedPreferGteLteOverGtLt() public { - assertTrue(course.unoptimizedPreferGteLteOverGtLt()); - } - - function testOptimizedPreferGteLteOverGtLt() public { - assertTrue(course.optimizedPreferGteLteOverGtLt()); - } - - function testUnoptimizedPreferNotEqualOverGtLt() public { - assertTrue(course.unoptimizedPreferNotEqualOverGtLt()); - } - - function testOptimizedPreferNotEqualOverGtLt() public { - assertTrue(course.optimizedPreferNotEqualOverGtLt()); - } - - function testUnoptimizedUseCodeLength() public { - assertTrue(course.unoptimizedUseCodeLength() > 0); - } - - function testOptimizedUseCodeLength() public { - assertTrue(course.optimizedUseCodeLength() > 0); - } -} diff --git a/src/contracts/OptimizedCacheArrLength.sol b/src/contracts/OptimizedCacheArrLength.sol new file mode 100644 index 0000000..53f4e73 --- /dev/null +++ b/src/contracts/OptimizedCacheArrLength.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract OptimizedCacheArrLength { + uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + function cacheArrLength() external view { + uint256 arrLength = arr.length; + for (uint256 index; index < arrLength; ++index) {} + } + +} diff --git a/src/contracts/OptimizedDivideByTwo.sol b/src/contracts/OptimizedDivideByTwo.sol new file mode 100644 index 0000000..d680f58 --- /dev/null +++ b/src/contracts/OptimizedDivideByTwo.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract OptimizedDivideByTwo { + function divideByTwo(uint256 four) external pure returns (uint256 two) { + /// 🚀 Optimized + two = four >> 1; + } + +} diff --git a/src/contracts/OptimizedPlusPlusIndex.sol b/src/contracts/OptimizedPlusPlusIndex.sol new file mode 100644 index 0000000..665f665 --- /dev/null +++ b/src/contracts/OptimizedPlusPlusIndex.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract OptimizedPlusPlusIndex { + uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + function plusPlusIndex() external view { + uint256 arrLength = arr.length; + for (uint256 index; index < arrLength; ++index) {} + } + +} diff --git a/src/contracts/OptimizedReentrancy.sol b/src/contracts/OptimizedReentrancy.sol new file mode 100644 index 0000000..c2f74da --- /dev/null +++ b/src/contracts/OptimizedReentrancy.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract OptimizedReentrancy { + event GenericEvent(); + + uint256 private locked = 1; + + modifier nonReentrant() { + /// 🚀 Optimized + require(locked == 1, "REENTRANCY"); + locked = 2; + _; + locked = 1; + } + function useUintForReentrancy() external nonReentrant returns(uint256 amount3) { + // do some stuff + uint256 amount1 = 1e18; + uint256 amount2 = 1e18; + amount3 = amount1 + amount2; + emit GenericEvent(); + } +} diff --git a/src/contracts/OptimizedUncheckedIncrement.sol b/src/contracts/OptimizedUncheckedIncrement.sol new file mode 100644 index 0000000..e91003f --- /dev/null +++ b/src/contracts/OptimizedUncheckedIncrement.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract OptimizedUncheckedIncrement { + uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + function _uncheckedIncrement(uint256 counter) private pure returns(uint256) { + unchecked { + return counter + 1; + } + } + function uncheckedIncrement() external view { + uint256 arrLength = arr.length; + for (uint256 index; index < arrLength; index = _uncheckedIncrement(index)) {} + } + +} diff --git a/src/contracts/OptimizedUseImmutable.sol b/src/contracts/OptimizedUseImmutable.sol new file mode 100644 index 0000000..40a1bc8 --- /dev/null +++ b/src/contracts/OptimizedUseImmutable.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract OptimizedUseImmutable { + uint256 public immutableNumber; + uint256 public storageNumber; + + constructor() { + immutableNumber = 1; + storageNumber = 1; + } + function useImmutable() external view returns(uint256) { + return immutableNumber + 1; + } + +} diff --git a/src/contracts/UnoptimizedCacheArrLength.sol b/src/contracts/UnoptimizedCacheArrLength.sol new file mode 100644 index 0000000..4562670 --- /dev/null +++ b/src/contracts/UnoptimizedCacheArrLength.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract UnoptimizedCacheArrLength { + uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + function cacheArrLength() external view { + for (uint256 index; index < arr.length; ++index) {} + } + +} diff --git a/src/contracts/UnoptimizedDivideByTwo.sol b/src/contracts/UnoptimizedDivideByTwo.sol new file mode 100644 index 0000000..10b26aa --- /dev/null +++ b/src/contracts/UnoptimizedDivideByTwo.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract UnoptimizedDivideByTwo { + + function divideByTwo(uint256 four) external pure returns (uint256 two) { + /// 🤦 Unoptimized + two = four / 2; + } + +} diff --git a/src/contracts/UnoptimizedPlusPlusIndex.sol b/src/contracts/UnoptimizedPlusPlusIndex.sol new file mode 100644 index 0000000..f8356fc --- /dev/null +++ b/src/contracts/UnoptimizedPlusPlusIndex.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract UnoptimizedPlusPlusIndex { + uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + function plusPlusIndex() external view { + uint256 arrLength = arr.length; + for (uint256 index; index < arrLength; index++) {} + } + +} diff --git a/src/contracts/UnoptimizedReentrancy.sol b/src/contracts/UnoptimizedReentrancy.sol new file mode 100644 index 0000000..7bb7216 --- /dev/null +++ b/src/contracts/UnoptimizedReentrancy.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract UnoptimizedReentrancy { + event GenericEvent(); + + bool private locked = false; + + modifier nonReentrant() { + /// 🤦 Unoptimized + require(locked == false, "REENTRANCY"); + locked = true; + _; + locked = false; + } + + function useUintForReentrancy() external nonReentrant returns(uint256 amount3) { + // do some stuff + uint256 amount1 = 1e18; + uint256 amount2 = 1e18; + amount3 = amount1 + amount2; + emit GenericEvent(); + } + +} diff --git a/src/contracts/UnoptimizedUncheckedIncrement.sol b/src/contracts/UnoptimizedUncheckedIncrement.sol new file mode 100644 index 0000000..1de4d23 --- /dev/null +++ b/src/contracts/UnoptimizedUncheckedIncrement.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract UnoptimizedUncheckedIncrement { + uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + function uncheckedIncrement() external view { + uint256 arrLength = arr.length; + for (uint256 index; index < arrLength; ++index) {} + } + +} diff --git a/src/contracts/UnoptimizedUseImmutable.sol b/src/contracts/UnoptimizedUseImmutable.sol new file mode 100644 index 0000000..7708764 --- /dev/null +++ b/src/contracts/UnoptimizedUseImmutable.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract UnoptimizedUseImmutable { + uint256 public immutableNumber; + uint256 public storageNumber; + + constructor() { + immutableNumber = 1; + storageNumber = 1; + } + function useImmutable() external view returns(uint256) { + return storageNumber + 1; + } + +} diff --git a/src/tests/Optimized.t.sol b/src/tests/Optimized.t.sol new file mode 100644 index 0000000..66e08d7 --- /dev/null +++ b/src/tests/Optimized.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +import "ds-test/test.sol"; + +import "./../contracts/OptimizedCacheArrLength.sol"; +import "./../contracts/OptimizedReentrancy.sol"; +import "./../contracts/OptimizedDivideByTwo.sol"; +import "./../contracts/OptimizedPlusPlusIndex.sol"; +import "./../contracts/OptimizedUncheckedIncrement.sol"; +import "./../contracts/OptimizedUseImmutable.sol"; + +contract OptimizedTest is DSTest { + OptimizedReentrancy public ctrctReentrancy; + OptimizedCacheArrLength public ctrctCacheArrLength; + OptimizedPlusPlusIndex public ctrctPlusPlusIndex; + OptimizedUncheckedIncrement public ctrctUncheckedIncrement; + OptimizedUseImmutable public ctrctUseImmutable; + OptimizedDivideByTwo public ctrctDivideByTwo; + + function setUp() public { + ctrctReentrancy = new OptimizedReentrancy(); + ctrctCacheArrLength = new OptimizedCacheArrLength(); + ctrctPlusPlusIndex = new OptimizedPlusPlusIndex(); + ctrctUseImmutable = new OptimizedUseImmutable(); + ctrctUncheckedIncrement = new OptimizedUncheckedIncrement(); + ctrctDivideByTwo = new OptimizedDivideByTwo(); + } + + function testCacheArrLength() public { + ctrctCacheArrLength.cacheArrLength(); + } + + function testPlusPlusIndex() public { + ctrctPlusPlusIndex.plusPlusIndex(); + } + + function testUncheckedIncrement() public { + ctrctUncheckedIncrement.uncheckedIncrement(); + } + + function testUseImmutable() public { + ctrctUseImmutable.useImmutable(); + } + + function testUseUintForReentrancy() public { + ctrctReentrancy.useUintForReentrancy(); + } + + function testDivideByTwo() public view { + ctrctDivideByTwo.divideByTwo(4); + } + + +} diff --git a/src/tests/Unoptimized.t.sol b/src/tests/Unoptimized.t.sol new file mode 100644 index 0000000..ef71188 --- /dev/null +++ b/src/tests/Unoptimized.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +import "ds-test/test.sol"; + +import "./../contracts/UnoptimizedCacheArrLength.sol"; +import "./../contracts/UnoptimizedPlusPlusIndex.sol"; +import "./../contracts/UnoptimizedReentrancy.sol"; +import "./../contracts/UnoptimizedDivideByTwo.sol"; +import "./../contracts/UnoptimizedUncheckedIncrement.sol"; +import "./../contracts/UnoptimizedUseImmutable.sol"; + +contract UnoptimizedTest is DSTest { + UnoptimizedCacheArrLength public ctrctCacheArrLength; + UnoptimizedPlusPlusIndex public ctrctPlusPlusIndex; + UnoptimizedUncheckedIncrement public ctrctUncheckedIncrement; + UnoptimizedReentrancy public ctrctReentrancy; + UnoptimizedDivideByTwo public ctrctDivideByTwo; + UnoptimizedUseImmutable public ctrctUseImmutable; + + function setUp() public { + ctrctCacheArrLength = new UnoptimizedCacheArrLength(); + ctrctPlusPlusIndex = new UnoptimizedPlusPlusIndex(); + ctrctUncheckedIncrement = new UnoptimizedUncheckedIncrement(); + ctrctReentrancy = new UnoptimizedReentrancy(); + ctrctDivideByTwo = new UnoptimizedDivideByTwo(); + ctrctUseImmutable = new UnoptimizedUseImmutable(); + } + + function testCacheArrLength() public { + ctrctCacheArrLength.cacheArrLength(); + } + + function testPlusPlusIndex() public { + ctrctPlusPlusIndex.plusPlusIndex(); + } + + function testUncheckedIncrement() public { + ctrctUncheckedIncrement.uncheckedIncrement(); + } + + function testUseImmutable() public { + ctrctUseImmutable.useImmutable(); + } + + function testUseUintForReentrancy() public { + ctrctReentrancy.useUintForReentrancy(); + } + + function testDivideByTwo() public view { + ctrctDivideByTwo.divideByTwo(4); + } + + +} From 48b3b6ec4d2ecc3694945e4ebcd8c239101d893d Mon Sep 17 00:00:00 2001 From: Richie Date: Sun, 13 Feb 2022 21:22:52 -0800 Subject: [PATCH 08/21] fix: typo --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d750ed0..9afdb8e 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ A list of common Solidity optimization tips and myths. bool private locked = false; modifier nonReentrant() { - require(locked == 1, "REENTRANCY"); - locked = 2; + require(locked == false, "REENTRANCY"); + locked = true; _; - locked = 1; + locked = false; } /// 🚀 Optimized (gas: 2053) From d33debe53dd9e7d93ca0a21fcd5903919ba4184e Mon Sep 17 00:00:00 2001 From: Richie Date: Tue, 15 Feb 2022 16:52:12 -0800 Subject: [PATCH 09/21] feat: update comments --- README.md | 16 ++++++++++++++++ src/contracts/OptimizedCacheArrLength.sol | 2 ++ src/contracts/OptimizedPlusPlusIndex.sol | 2 ++ src/contracts/OptimizedUncheckedIncrement.sol | 2 ++ src/contracts/OptimizedUseImmutable.sol | 1 + src/contracts/UnoptimizedCacheArrLength.sol | 2 ++ src/contracts/UnoptimizedPlusPlusIndex.sol | 2 ++ src/contracts/UnoptimizedReentrancy.sol | 1 - src/contracts/UnoptimizedUncheckedIncrement.sol | 2 ++ src/contracts/UnoptimizedUseImmutable.sol | 1 + 10 files changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9afdb8e..6c544a5 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,22 @@ The `SHR` opcode is 3 gas cheaper than `DIV` and more imporantly, bypasses Solid - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedDivideByTwo.sol) +- - - - +### In require(), use != 0 instead of > 0 with UINTs ### + +```solidity +uint256 notZero = 4; + +/// 🤦 Unoptimized (gas: 942) +require(notZero > 0); + +/// 🚀 Optimized (gas: 866) +require(notZero != 0); +``` +In a require, when checking a UINT, using != 0 instead of > 0 saves 6 gas. Note: This only works in require but not in other situations. For more info see [this thread](https://twitter.com/transmissions11/status/1469848358558711808?s=20&t=hyTZxmZKXq06opE8wgo1aA) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedRequireNeZero.sol) + + # Myths diff --git a/src/contracts/OptimizedCacheArrLength.sol b/src/contracts/OptimizedCacheArrLength.sol index 53f4e73..c9d190e 100644 --- a/src/contracts/OptimizedCacheArrLength.sol +++ b/src/contracts/OptimizedCacheArrLength.sol @@ -6,6 +6,8 @@ contract OptimizedCacheArrLength { function cacheArrLength() external view { uint256 arrLength = arr.length; + + /// 🚀 Optimized for (uint256 index; index < arrLength; ++index) {} } diff --git a/src/contracts/OptimizedPlusPlusIndex.sol b/src/contracts/OptimizedPlusPlusIndex.sol index 665f665..79efa43 100644 --- a/src/contracts/OptimizedPlusPlusIndex.sol +++ b/src/contracts/OptimizedPlusPlusIndex.sol @@ -6,6 +6,8 @@ contract OptimizedPlusPlusIndex { function plusPlusIndex() external view { uint256 arrLength = arr.length; + + /// 🚀 Optimized for (uint256 index; index < arrLength; ++index) {} } diff --git a/src/contracts/OptimizedUncheckedIncrement.sol b/src/contracts/OptimizedUncheckedIncrement.sol index e91003f..e192c0a 100644 --- a/src/contracts/OptimizedUncheckedIncrement.sol +++ b/src/contracts/OptimizedUncheckedIncrement.sol @@ -11,6 +11,8 @@ contract OptimizedUncheckedIncrement { } function uncheckedIncrement() external view { uint256 arrLength = arr.length; + + /// 🚀 Optimized for (uint256 index; index < arrLength; index = _uncheckedIncrement(index)) {} } diff --git a/src/contracts/OptimizedUseImmutable.sol b/src/contracts/OptimizedUseImmutable.sol index 40a1bc8..5c1aee3 100644 --- a/src/contracts/OptimizedUseImmutable.sol +++ b/src/contracts/OptimizedUseImmutable.sol @@ -10,6 +10,7 @@ contract OptimizedUseImmutable { storageNumber = 1; } function useImmutable() external view returns(uint256) { + /// 🚀 Optimized return immutableNumber + 1; } diff --git a/src/contracts/UnoptimizedCacheArrLength.sol b/src/contracts/UnoptimizedCacheArrLength.sol index 4562670..e6dd1fa 100644 --- a/src/contracts/UnoptimizedCacheArrLength.sol +++ b/src/contracts/UnoptimizedCacheArrLength.sol @@ -5,6 +5,8 @@ contract UnoptimizedCacheArrLength { uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; function cacheArrLength() external view { + /// 🤦 Unoptimized + for (uint256 index; index < arr.length; ++index) {} } diff --git a/src/contracts/UnoptimizedPlusPlusIndex.sol b/src/contracts/UnoptimizedPlusPlusIndex.sol index f8356fc..167a602 100644 --- a/src/contracts/UnoptimizedPlusPlusIndex.sol +++ b/src/contracts/UnoptimizedPlusPlusIndex.sol @@ -6,6 +6,8 @@ contract UnoptimizedPlusPlusIndex { function plusPlusIndex() external view { uint256 arrLength = arr.length; + + /// 🤦 Unoptimized for (uint256 index; index < arrLength; index++) {} } diff --git a/src/contracts/UnoptimizedReentrancy.sol b/src/contracts/UnoptimizedReentrancy.sol index 7bb7216..01f6185 100644 --- a/src/contracts/UnoptimizedReentrancy.sol +++ b/src/contracts/UnoptimizedReentrancy.sol @@ -21,5 +21,4 @@ contract UnoptimizedReentrancy { amount3 = amount1 + amount2; emit GenericEvent(); } - } diff --git a/src/contracts/UnoptimizedUncheckedIncrement.sol b/src/contracts/UnoptimizedUncheckedIncrement.sol index 1de4d23..6d70c56 100644 --- a/src/contracts/UnoptimizedUncheckedIncrement.sol +++ b/src/contracts/UnoptimizedUncheckedIncrement.sol @@ -6,6 +6,8 @@ contract UnoptimizedUncheckedIncrement { function uncheckedIncrement() external view { uint256 arrLength = arr.length; + + /// 🤦 Unoptimized for (uint256 index; index < arrLength; ++index) {} } diff --git a/src/contracts/UnoptimizedUseImmutable.sol b/src/contracts/UnoptimizedUseImmutable.sol index 7708764..75fc1f2 100644 --- a/src/contracts/UnoptimizedUseImmutable.sol +++ b/src/contracts/UnoptimizedUseImmutable.sol @@ -10,6 +10,7 @@ contract UnoptimizedUseImmutable { storageNumber = 1; } function useImmutable() external view returns(uint256) { + /// 🤦 Unoptimized return storageNumber + 1; } From b814df29486f4aa008c48647cbaeda388e21eed4 Mon Sep 17 00:00:00 2001 From: Richie Date: Tue, 15 Feb 2022 16:52:40 -0800 Subject: [PATCH 10/21] feat: add RequireNeZero tip --- src/contracts/OptimizedRequireNeZero.sol | 10 ++++++++++ src/contracts/UnoptimizedRequireNeZero.sol | 10 ++++++++++ src/tests/Optimized.t.sol | 7 +++++++ src/tests/Unoptimized.t.sol | 7 +++++++ 4 files changed, 34 insertions(+) create mode 100644 src/contracts/OptimizedRequireNeZero.sol create mode 100644 src/contracts/UnoptimizedRequireNeZero.sol diff --git a/src/contracts/OptimizedRequireNeZero.sol b/src/contracts/OptimizedRequireNeZero.sol new file mode 100644 index 0000000..4f32ab3 --- /dev/null +++ b/src/contracts/OptimizedRequireNeZero.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract OptimizedRequireNeZero { + function requireNeZero(uint256 notZero) external pure { + /// 🚀 Optimized + require(notZero != 0); + } + +} diff --git a/src/contracts/UnoptimizedRequireNeZero.sol b/src/contracts/UnoptimizedRequireNeZero.sol new file mode 100644 index 0000000..949dbec --- /dev/null +++ b/src/contracts/UnoptimizedRequireNeZero.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract UnoptimizedRequireNeZero { + function requireNeZero(uint256 notZero) external pure { + /// 🤦 Unoptimized + require(notZero > 0); + } + +} diff --git a/src/tests/Optimized.t.sol b/src/tests/Optimized.t.sol index 66e08d7..76677c2 100644 --- a/src/tests/Optimized.t.sol +++ b/src/tests/Optimized.t.sol @@ -9,6 +9,7 @@ import "./../contracts/OptimizedDivideByTwo.sol"; import "./../contracts/OptimizedPlusPlusIndex.sol"; import "./../contracts/OptimizedUncheckedIncrement.sol"; import "./../contracts/OptimizedUseImmutable.sol"; +import "./../contracts/OptimizedRequireNeZero.sol"; contract OptimizedTest is DSTest { OptimizedReentrancy public ctrctReentrancy; @@ -16,6 +17,7 @@ contract OptimizedTest is DSTest { OptimizedPlusPlusIndex public ctrctPlusPlusIndex; OptimizedUncheckedIncrement public ctrctUncheckedIncrement; OptimizedUseImmutable public ctrctUseImmutable; + OptimizedRequireNeZero public ctrctRequireNeZero; OptimizedDivideByTwo public ctrctDivideByTwo; function setUp() public { @@ -23,10 +25,15 @@ contract OptimizedTest is DSTest { ctrctCacheArrLength = new OptimizedCacheArrLength(); ctrctPlusPlusIndex = new OptimizedPlusPlusIndex(); ctrctUseImmutable = new OptimizedUseImmutable(); + ctrctRequireNeZero = new OptimizedRequireNeZero(); ctrctUncheckedIncrement = new OptimizedUncheckedIncrement(); ctrctDivideByTwo = new OptimizedDivideByTwo(); } + function testRequireNeZero() public { + ctrctRequireNeZero.requireNeZero(4); + } + function testCacheArrLength() public { ctrctCacheArrLength.cacheArrLength(); } diff --git a/src/tests/Unoptimized.t.sol b/src/tests/Unoptimized.t.sol index ef71188..45d68cd 100644 --- a/src/tests/Unoptimized.t.sol +++ b/src/tests/Unoptimized.t.sol @@ -9,6 +9,7 @@ import "./../contracts/UnoptimizedReentrancy.sol"; import "./../contracts/UnoptimizedDivideByTwo.sol"; import "./../contracts/UnoptimizedUncheckedIncrement.sol"; import "./../contracts/UnoptimizedUseImmutable.sol"; +import "./../contracts/UnoptimizedRequireNeZero.sol"; contract UnoptimizedTest is DSTest { UnoptimizedCacheArrLength public ctrctCacheArrLength; @@ -17,6 +18,7 @@ contract UnoptimizedTest is DSTest { UnoptimizedReentrancy public ctrctReentrancy; UnoptimizedDivideByTwo public ctrctDivideByTwo; UnoptimizedUseImmutable public ctrctUseImmutable; + UnoptimizedRequireNeZero public ctrctRequireNeZero; function setUp() public { ctrctCacheArrLength = new UnoptimizedCacheArrLength(); @@ -25,6 +27,11 @@ contract UnoptimizedTest is DSTest { ctrctReentrancy = new UnoptimizedReentrancy(); ctrctDivideByTwo = new UnoptimizedDivideByTwo(); ctrctUseImmutable = new UnoptimizedUseImmutable(); + ctrctRequireNeZero = new UnoptimizedRequireNeZero(); + } + + function testRequireNeZero() public { + ctrctRequireNeZero.requireNeZero(4); } function testCacheArrLength() public { From f45ed35da9adcee422101df5bcf2c3661473dfe9 Mon Sep 17 00:00:00 2001 From: Richie Date: Tue, 15 Feb 2022 16:53:40 -0800 Subject: [PATCH 11/21] tweak: update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e2e7327..e6aca48 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /out +cache/ From 70d07b7785abf57267ad9cec8bf2712574c019e8 Mon Sep 17 00:00:00 2001 From: Richie Date: Tue, 15 Feb 2022 16:59:08 -0800 Subject: [PATCH 12/21] fix: update directory structure to eliminate differences in contract names --- .../CacheArrLength.sol} | 2 +- .../DivideByTwo.sol} | 2 +- .../PlusPlusIndex.sol} | 2 +- .../Reentrancy.sol} | 2 +- .../RequireNeZero.sol} | 2 +- .../UncheckedIncrement.sol} | 2 +- .../UseImmutable.sol} | 2 +- .../CacheArrLength.sol} | 2 +- .../DivideByTwo.sol} | 2 +- .../PlusPlusIndex.sol} | 2 +- .../Reentrancy.sol} | 2 +- .../RequireNeZero.sol} | 2 +- .../UncheckedIncrement.sol} | 2 +- .../UseImmutable.sol} | 2 +- src/tests/Optimized.t.sol | 46 +++++++++---------- src/tests/Unoptimized.t.sol | 46 +++++++++---------- 16 files changed, 60 insertions(+), 60 deletions(-) rename src/contracts/{OptimizedCacheArrLength.sol => optimized/CacheArrLength.sol} (90%) rename src/contracts/{OptimizedDivideByTwo.sol => optimized/DivideByTwo.sol} (86%) rename src/contracts/{OptimizedPlusPlusIndex.sol => optimized/PlusPlusIndex.sol} (90%) rename src/contracts/{OptimizedReentrancy.sol => optimized/Reentrancy.sol} (94%) rename src/contracts/{OptimizedRequireNeZero.sol => optimized/RequireNeZero.sol} (85%) rename src/contracts/{OptimizedUncheckedIncrement.sol => optimized/UncheckedIncrement.sol} (92%) rename src/contracts/{OptimizedUseImmutable.sol => optimized/UseImmutable.sol} (91%) rename src/contracts/{UnoptimizedCacheArrLength.sol => unoptimized/CacheArrLength.sol} (88%) rename src/contracts/{UnoptimizedDivideByTwo.sol => unoptimized/DivideByTwo.sol} (86%) rename src/contracts/{UnoptimizedPlusPlusIndex.sol => unoptimized/PlusPlusIndex.sol} (90%) rename src/contracts/{UnoptimizedReentrancy.sol => unoptimized/Reentrancy.sol} (94%) rename src/contracts/{UnoptimizedRequireNeZero.sol => unoptimized/RequireNeZero.sol} (84%) rename src/contracts/{UnoptimizedUncheckedIncrement.sol => unoptimized/UncheckedIncrement.sol} (88%) rename src/contracts/{UnoptimizedUseImmutable.sol => unoptimized/UseImmutable.sol} (91%) diff --git a/src/contracts/OptimizedCacheArrLength.sol b/src/contracts/optimized/CacheArrLength.sol similarity index 90% rename from src/contracts/OptimizedCacheArrLength.sol rename to src/contracts/optimized/CacheArrLength.sol index c9d190e..ae1fd74 100644 --- a/src/contracts/OptimizedCacheArrLength.sol +++ b/src/contracts/optimized/CacheArrLength.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract OptimizedCacheArrLength { +contract CacheArrLength { uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; function cacheArrLength() external view { diff --git a/src/contracts/OptimizedDivideByTwo.sol b/src/contracts/optimized/DivideByTwo.sol similarity index 86% rename from src/contracts/OptimizedDivideByTwo.sol rename to src/contracts/optimized/DivideByTwo.sol index d680f58..917c306 100644 --- a/src/contracts/OptimizedDivideByTwo.sol +++ b/src/contracts/optimized/DivideByTwo.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract OptimizedDivideByTwo { +contract DivideByTwo { function divideByTwo(uint256 four) external pure returns (uint256 two) { /// 🚀 Optimized two = four >> 1; diff --git a/src/contracts/OptimizedPlusPlusIndex.sol b/src/contracts/optimized/PlusPlusIndex.sol similarity index 90% rename from src/contracts/OptimizedPlusPlusIndex.sol rename to src/contracts/optimized/PlusPlusIndex.sol index 79efa43..7d15291 100644 --- a/src/contracts/OptimizedPlusPlusIndex.sol +++ b/src/contracts/optimized/PlusPlusIndex.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract OptimizedPlusPlusIndex { +contract PlusPlusIndex { uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; function plusPlusIndex() external view { diff --git a/src/contracts/OptimizedReentrancy.sol b/src/contracts/optimized/Reentrancy.sol similarity index 94% rename from src/contracts/OptimizedReentrancy.sol rename to src/contracts/optimized/Reentrancy.sol index c2f74da..5e8430d 100644 --- a/src/contracts/OptimizedReentrancy.sol +++ b/src/contracts/optimized/Reentrancy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract OptimizedReentrancy { +contract Reentrancy { event GenericEvent(); uint256 private locked = 1; diff --git a/src/contracts/OptimizedRequireNeZero.sol b/src/contracts/optimized/RequireNeZero.sol similarity index 85% rename from src/contracts/OptimizedRequireNeZero.sol rename to src/contracts/optimized/RequireNeZero.sol index 4f32ab3..f51f990 100644 --- a/src/contracts/OptimizedRequireNeZero.sol +++ b/src/contracts/optimized/RequireNeZero.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract OptimizedRequireNeZero { +contract RequireNeZero { function requireNeZero(uint256 notZero) external pure { /// 🚀 Optimized require(notZero != 0); diff --git a/src/contracts/OptimizedUncheckedIncrement.sol b/src/contracts/optimized/UncheckedIncrement.sol similarity index 92% rename from src/contracts/OptimizedUncheckedIncrement.sol rename to src/contracts/optimized/UncheckedIncrement.sol index e192c0a..d01193a 100644 --- a/src/contracts/OptimizedUncheckedIncrement.sol +++ b/src/contracts/optimized/UncheckedIncrement.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract OptimizedUncheckedIncrement { +contract UncheckedIncrement { uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; function _uncheckedIncrement(uint256 counter) private pure returns(uint256) { diff --git a/src/contracts/OptimizedUseImmutable.sol b/src/contracts/optimized/UseImmutable.sol similarity index 91% rename from src/contracts/OptimizedUseImmutable.sol rename to src/contracts/optimized/UseImmutable.sol index 5c1aee3..5812592 100644 --- a/src/contracts/OptimizedUseImmutable.sol +++ b/src/contracts/optimized/UseImmutable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract OptimizedUseImmutable { +contract UseImmutable { uint256 public immutableNumber; uint256 public storageNumber; diff --git a/src/contracts/UnoptimizedCacheArrLength.sol b/src/contracts/unoptimized/CacheArrLength.sol similarity index 88% rename from src/contracts/UnoptimizedCacheArrLength.sol rename to src/contracts/unoptimized/CacheArrLength.sol index e6dd1fa..fd6c916 100644 --- a/src/contracts/UnoptimizedCacheArrLength.sol +++ b/src/contracts/unoptimized/CacheArrLength.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract UnoptimizedCacheArrLength { +contract CacheArrLength { uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; function cacheArrLength() external view { diff --git a/src/contracts/UnoptimizedDivideByTwo.sol b/src/contracts/unoptimized/DivideByTwo.sol similarity index 86% rename from src/contracts/UnoptimizedDivideByTwo.sol rename to src/contracts/unoptimized/DivideByTwo.sol index 10b26aa..58092f0 100644 --- a/src/contracts/UnoptimizedDivideByTwo.sol +++ b/src/contracts/unoptimized/DivideByTwo.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract UnoptimizedDivideByTwo { +contract DivideByTwo { function divideByTwo(uint256 four) external pure returns (uint256 two) { /// 🤦 Unoptimized diff --git a/src/contracts/UnoptimizedPlusPlusIndex.sol b/src/contracts/unoptimized/PlusPlusIndex.sol similarity index 90% rename from src/contracts/UnoptimizedPlusPlusIndex.sol rename to src/contracts/unoptimized/PlusPlusIndex.sol index 167a602..0f8d659 100644 --- a/src/contracts/UnoptimizedPlusPlusIndex.sol +++ b/src/contracts/unoptimized/PlusPlusIndex.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract UnoptimizedPlusPlusIndex { +contract PlusPlusIndex { uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; function plusPlusIndex() external view { diff --git a/src/contracts/UnoptimizedReentrancy.sol b/src/contracts/unoptimized/Reentrancy.sol similarity index 94% rename from src/contracts/UnoptimizedReentrancy.sol rename to src/contracts/unoptimized/Reentrancy.sol index 01f6185..626fe84 100644 --- a/src/contracts/UnoptimizedReentrancy.sol +++ b/src/contracts/unoptimized/Reentrancy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract UnoptimizedReentrancy { +contract Reentrancy { event GenericEvent(); bool private locked = false; diff --git a/src/contracts/UnoptimizedRequireNeZero.sol b/src/contracts/unoptimized/RequireNeZero.sol similarity index 84% rename from src/contracts/UnoptimizedRequireNeZero.sol rename to src/contracts/unoptimized/RequireNeZero.sol index 949dbec..ca1da25 100644 --- a/src/contracts/UnoptimizedRequireNeZero.sol +++ b/src/contracts/unoptimized/RequireNeZero.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract UnoptimizedRequireNeZero { +contract RequireNeZero { function requireNeZero(uint256 notZero) external pure { /// 🤦 Unoptimized require(notZero > 0); diff --git a/src/contracts/UnoptimizedUncheckedIncrement.sol b/src/contracts/unoptimized/UncheckedIncrement.sol similarity index 88% rename from src/contracts/UnoptimizedUncheckedIncrement.sol rename to src/contracts/unoptimized/UncheckedIncrement.sol index 6d70c56..a678cc2 100644 --- a/src/contracts/UnoptimizedUncheckedIncrement.sol +++ b/src/contracts/unoptimized/UncheckedIncrement.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract UnoptimizedUncheckedIncrement { +contract UncheckedIncrement { uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; function uncheckedIncrement() external view { diff --git a/src/contracts/UnoptimizedUseImmutable.sol b/src/contracts/unoptimized/UseImmutable.sol similarity index 91% rename from src/contracts/UnoptimizedUseImmutable.sol rename to src/contracts/unoptimized/UseImmutable.sol index 75fc1f2..9396195 100644 --- a/src/contracts/UnoptimizedUseImmutable.sol +++ b/src/contracts/unoptimized/UseImmutable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.11; -contract UnoptimizedUseImmutable { +contract UseImmutable { uint256 public immutableNumber; uint256 public storageNumber; diff --git a/src/tests/Optimized.t.sol b/src/tests/Optimized.t.sol index 76677c2..e56887c 100644 --- a/src/tests/Optimized.t.sol +++ b/src/tests/Optimized.t.sol @@ -3,31 +3,31 @@ pragma solidity ^0.8.11; import "ds-test/test.sol"; -import "./../contracts/OptimizedCacheArrLength.sol"; -import "./../contracts/OptimizedReentrancy.sol"; -import "./../contracts/OptimizedDivideByTwo.sol"; -import "./../contracts/OptimizedPlusPlusIndex.sol"; -import "./../contracts/OptimizedUncheckedIncrement.sol"; -import "./../contracts/OptimizedUseImmutable.sol"; -import "./../contracts/OptimizedRequireNeZero.sol"; - -contract OptimizedTest is DSTest { - OptimizedReentrancy public ctrctReentrancy; - OptimizedCacheArrLength public ctrctCacheArrLength; - OptimizedPlusPlusIndex public ctrctPlusPlusIndex; - OptimizedUncheckedIncrement public ctrctUncheckedIncrement; - OptimizedUseImmutable public ctrctUseImmutable; - OptimizedRequireNeZero public ctrctRequireNeZero; - OptimizedDivideByTwo public ctrctDivideByTwo; +import "./../contracts/optimized/CacheArrLength.sol"; +import "./../contracts/optimized/Reentrancy.sol"; +import "./../contracts/optimized/DivideByTwo.sol"; +import "./../contracts/optimized/PlusPlusIndex.sol"; +import "./../contracts/optimized/UncheckedIncrement.sol"; +import "./../contracts/optimized/UseImmutable.sol"; +import "./../contracts/optimized/RequireNeZero.sol"; + +contract Test is DSTest { + Reentrancy public ctrctReentrancy; + CacheArrLength public ctrctCacheArrLength; + PlusPlusIndex public ctrctPlusPlusIndex; + UncheckedIncrement public ctrctUncheckedIncrement; + UseImmutable public ctrctUseImmutable; + RequireNeZero public ctrctRequireNeZero; + DivideByTwo public ctrctDivideByTwo; function setUp() public { - ctrctReentrancy = new OptimizedReentrancy(); - ctrctCacheArrLength = new OptimizedCacheArrLength(); - ctrctPlusPlusIndex = new OptimizedPlusPlusIndex(); - ctrctUseImmutable = new OptimizedUseImmutable(); - ctrctRequireNeZero = new OptimizedRequireNeZero(); - ctrctUncheckedIncrement = new OptimizedUncheckedIncrement(); - ctrctDivideByTwo = new OptimizedDivideByTwo(); + ctrctReentrancy = new Reentrancy(); + ctrctCacheArrLength = new CacheArrLength(); + ctrctPlusPlusIndex = new PlusPlusIndex(); + ctrctUseImmutable = new UseImmutable(); + ctrctRequireNeZero = new RequireNeZero(); + ctrctUncheckedIncrement = new UncheckedIncrement(); + ctrctDivideByTwo = new DivideByTwo(); } function testRequireNeZero() public { diff --git a/src/tests/Unoptimized.t.sol b/src/tests/Unoptimized.t.sol index 45d68cd..1845e18 100644 --- a/src/tests/Unoptimized.t.sol +++ b/src/tests/Unoptimized.t.sol @@ -3,31 +3,31 @@ pragma solidity ^0.8.11; import "ds-test/test.sol"; -import "./../contracts/UnoptimizedCacheArrLength.sol"; -import "./../contracts/UnoptimizedPlusPlusIndex.sol"; -import "./../contracts/UnoptimizedReentrancy.sol"; -import "./../contracts/UnoptimizedDivideByTwo.sol"; -import "./../contracts/UnoptimizedUncheckedIncrement.sol"; -import "./../contracts/UnoptimizedUseImmutable.sol"; -import "./../contracts/UnoptimizedRequireNeZero.sol"; - -contract UnoptimizedTest is DSTest { - UnoptimizedCacheArrLength public ctrctCacheArrLength; - UnoptimizedPlusPlusIndex public ctrctPlusPlusIndex; - UnoptimizedUncheckedIncrement public ctrctUncheckedIncrement; - UnoptimizedReentrancy public ctrctReentrancy; - UnoptimizedDivideByTwo public ctrctDivideByTwo; - UnoptimizedUseImmutable public ctrctUseImmutable; - UnoptimizedRequireNeZero public ctrctRequireNeZero; +import "./../contracts/unoptimized/CacheArrLength.sol"; +import "./../contracts/unoptimized/PlusPlusIndex.sol"; +import "./../contracts/unoptimized/Reentrancy.sol"; +import "./../contracts/unoptimized/DivideByTwo.sol"; +import "./../contracts/unoptimized/UncheckedIncrement.sol"; +import "./../contracts/unoptimized/UseImmutable.sol"; +import "./../contracts/unoptimized/RequireNeZero.sol"; + +contract Test is DSTest { + CacheArrLength public ctrctCacheArrLength; + PlusPlusIndex public ctrctPlusPlusIndex; + UncheckedIncrement public ctrctUncheckedIncrement; + Reentrancy public ctrctReentrancy; + DivideByTwo public ctrctDivideByTwo; + UseImmutable public ctrctUseImmutable; + RequireNeZero public ctrctRequireNeZero; function setUp() public { - ctrctCacheArrLength = new UnoptimizedCacheArrLength(); - ctrctPlusPlusIndex = new UnoptimizedPlusPlusIndex(); - ctrctUncheckedIncrement = new UnoptimizedUncheckedIncrement(); - ctrctReentrancy = new UnoptimizedReentrancy(); - ctrctDivideByTwo = new UnoptimizedDivideByTwo(); - ctrctUseImmutable = new UnoptimizedUseImmutable(); - ctrctRequireNeZero = new UnoptimizedRequireNeZero(); + ctrctCacheArrLength = new CacheArrLength(); + ctrctPlusPlusIndex = new PlusPlusIndex(); + ctrctUncheckedIncrement = new UncheckedIncrement(); + ctrctReentrancy = new Reentrancy(); + ctrctDivideByTwo = new DivideByTwo(); + ctrctUseImmutable = new UseImmutable(); + ctrctRequireNeZero = new RequireNeZero(); } function testRequireNeZero() public { From 56068113b6664d74ed993efdc29376182fd6e083 Mon Sep 17 00:00:00 2001 From: Richie Date: Tue, 15 Feb 2022 17:02:44 -0800 Subject: [PATCH 13/21] fix: update gas usage from new ctrct names --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6c544a5..593c804 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A list of common Solidity optimization tips and myths. ### Use UINT instead of BOOL for reentrancy guard ### ```solidity -/// 🤦 Unoptimized (gas: 22180) +/// 🤦 Unoptimized (gas: 22202) bool private locked = false; modifier nonReentrant() { @@ -18,7 +18,7 @@ modifier nonReentrant() { locked = false; } -/// 🚀 Optimized (gas: 2053) +/// 🚀 Optimized (gas: 2125) bool private locked = 1; modifier nonReentrant() { require(locked == 1, "REENTRANCY"); @@ -37,10 +37,10 @@ Use a reentrancy guard like [Solmate](https://github.com/Rari-Capital/solmate/bl ```solidity uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10]; -/// 🤦 Unoptimized (gas: 3077) +/// 🤦 Unoptimized (gas: 3055) for (uint256 index; index < arr.length; ++index) {} -/// 🚀 Optimized (gas: 2085) +/// 🚀 Optimized (gas: 2013) uint256 arrLength = arr.length; for (uint256 index; index < arrLength; ++index) {} ``` @@ -52,11 +52,11 @@ Caching the array length first saves an SLOAD on each iteration of the loop. ```solidity -/// 🤦 Unoptimized (gas: 1990) +/// 🤦 Unoptimized (gas: 2035) for (uint256 index; index < arrLength; ++index) {} -/// 🚀 Optimized (gas: 1315) +/// 🚀 Optimized (gas: 1375) function _uncheckedIncrement(uint256 counter) private pure returns(uint256) { unchecked { return counter + 1; @@ -105,10 +105,10 @@ Each storage read of the state variable is replaced by the instruction push32 va uint256 four = 4; uint256 two; -/// 🤦 Unoptimized (gas: 942) +/// 🤦 Unoptimized (gas: 1012) two = four / 2; -/// 🚀 Optimized (gas: 866) +/// 🚀 Optimized (gas: 933) two = four >> 1; ``` @@ -122,10 +122,10 @@ The `SHR` opcode is 3 gas cheaper than `DIV` and more imporantly, bypasses Solid ```solidity uint256 notZero = 4; -/// 🤦 Unoptimized (gas: 942) +/// 🤦 Unoptimized (gas: 867) require(notZero > 0); -/// 🚀 Optimized (gas: 866) +/// 🚀 Optimized (gas: 861) require(notZero != 0); ``` In a require, when checking a UINT, using != 0 instead of > 0 saves 6 gas. Note: This only works in require but not in other situations. For more info see [this thread](https://twitter.com/transmissions11/status/1469848358558711808?s=20&t=hyTZxmZKXq06opE8wgo1aA) From 52b02b009d331844669604fac9d1164cf136d905 Mon Sep 17 00:00:00 2001 From: Richie Date: Tue, 15 Feb 2022 17:23:35 -0800 Subject: [PATCH 14/21] fix: update links in readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 593c804..c8ed8f0 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ modifier nonReentrant() { ``` Use a reentrancy guard like [Solmate](https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol) which employs uint instead of boolean storage variable which saves gas. - - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedReentrancy.sol) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/Reentrancy.sol) - - - - ### When iterating through a storage array, cache the array length first ### @@ -45,7 +45,7 @@ uint256 arrLength = arr.length; for (uint256 index; index < arrLength; ++index) {} ``` Caching the array length first saves an SLOAD on each iteration of the loop. - - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedCacheArrLength.sol) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/CacheArrLength.sol) - - - - ### Use unchecked with counter incrementing logic ### @@ -66,7 +66,7 @@ function _uncheckedIncrement(uint256 counter) private pure returns(uint256) { for (uint256 index; index < arrLength; index = _uncheckedIncrement(index)) {} ``` It is a logical impossibility for index to overflow if it is always less than another integer (index < arrLength). Skipping the unchecked saves ~60 gas per iteration. Note: Part of the savings here comes from the fact that as of Solidity 0.8.2, the compiler will inline this function automatically. Using an older pragma would reduce the gas savings. For additional info see [hrkrshnn's writeup](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc) - - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedUncheckedIncrement.sol) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/UncheckedIncrement.sol) - - - - @@ -80,7 +80,7 @@ for (uint256 index; index < arrLength; index++) {} for (uint256 index; index < arrLength; ++index) {} ``` Due to reduced stack operations, using ++index saves 5 gas per iteration - - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedPlusPlusIndex.sol) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/PlusPlusIndex.sol) - - - - ### Prefer using immutable to storage ### @@ -113,7 +113,7 @@ two = four >> 1; ``` The `SHR` opcode is 3 gas cheaper than `DIV` and more imporantly, bypasses Solidity's division by 0 prevention overhead. This can be used not only when dividing by two, but with any exponent of 2. - - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedDivideByTwo.sol) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/DivideByTwo.sol) - - - - @@ -129,7 +129,7 @@ require(notZero > 0); require(notZero != 0); ``` In a require, when checking a UINT, using != 0 instead of > 0 saves 6 gas. Note: This only works in require but not in other situations. For more info see [this thread](https://twitter.com/transmissions11/status/1469848358558711808?s=20&t=hyTZxmZKXq06opE8wgo1aA) - - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedRequireNeZero.sol) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/RequireNeZero.sol) From 00d2a4f12d7d131688c6734ea0644d82754b25de Mon Sep 17 00:00:00 2001 From: Richie Date: Tue, 15 Feb 2022 17:23:48 -0800 Subject: [PATCH 15/21] feat: add payable functions tip --- README.md | 16 +++++++++++++++- src/tests/Optimized.t.sol | 7 +++++++ src/tests/Unoptimized.t.sol | 6 ++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c8ed8f0..3da4a05 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,21 @@ uint256 sum = storageNumber + 1; uint256 sum = immutableNumber + 1; ``` Each storage read of the state variable is replaced by the instruction push32 value, where value is set during contract construction time. For additional info see [hrkrshnn's writeup](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc) - - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/OptimizedUseImmutable.sol) + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/UseImmutable.sol) + +- - - - +### Make functions payable ### + +```solidity + +/// 🤦 Unoptimized (gas: 781) +function doSomething() external pure {} + +/// 🚀 Optimized (gas: 760) +function doSomething() payable external {} +``` +Making functions payable eliminates the need for an initial check of msg.value == 0 and saves 21 gas. Note: This conservatively assumes the function could be pure if not for the payable. When compared against an non-pure function the savings is more. Note: For certain contracts, adding a payable function where none existed previously could introduce a security risk. Use with caution. + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/PayableFunctions.sol) - - - - ### When dividing by two, use `>> 1` instead of `/ 2` ### diff --git a/src/tests/Optimized.t.sol b/src/tests/Optimized.t.sol index e56887c..92d275d 100644 --- a/src/tests/Optimized.t.sol +++ b/src/tests/Optimized.t.sol @@ -10,6 +10,7 @@ import "./../contracts/optimized/PlusPlusIndex.sol"; import "./../contracts/optimized/UncheckedIncrement.sol"; import "./../contracts/optimized/UseImmutable.sol"; import "./../contracts/optimized/RequireNeZero.sol"; +import "./../contracts/optimized/PayableFunctions.sol"; contract Test is DSTest { Reentrancy public ctrctReentrancy; @@ -19,6 +20,7 @@ contract Test is DSTest { UseImmutable public ctrctUseImmutable; RequireNeZero public ctrctRequireNeZero; DivideByTwo public ctrctDivideByTwo; + PayableFunctions public ctrctPayableFunctions; function setUp() public { ctrctReentrancy = new Reentrancy(); @@ -28,6 +30,11 @@ contract Test is DSTest { ctrctRequireNeZero = new RequireNeZero(); ctrctUncheckedIncrement = new UncheckedIncrement(); ctrctDivideByTwo = new DivideByTwo(); + ctrctPayableFunctions = new PayableFunctions(); + } + + function testPayableFunctions() public { + ctrctPayableFunctions.payableFunctions(); } function testRequireNeZero() public { diff --git a/src/tests/Unoptimized.t.sol b/src/tests/Unoptimized.t.sol index 1845e18..f6f53d1 100644 --- a/src/tests/Unoptimized.t.sol +++ b/src/tests/Unoptimized.t.sol @@ -10,6 +10,7 @@ import "./../contracts/unoptimized/DivideByTwo.sol"; import "./../contracts/unoptimized/UncheckedIncrement.sol"; import "./../contracts/unoptimized/UseImmutable.sol"; import "./../contracts/unoptimized/RequireNeZero.sol"; +import "./../contracts/unoptimized/PayableFunctions.sol"; contract Test is DSTest { CacheArrLength public ctrctCacheArrLength; @@ -19,6 +20,7 @@ contract Test is DSTest { DivideByTwo public ctrctDivideByTwo; UseImmutable public ctrctUseImmutable; RequireNeZero public ctrctRequireNeZero; + PayableFunctions public ctrctPayableFunctions; function setUp() public { ctrctCacheArrLength = new CacheArrLength(); @@ -28,8 +30,12 @@ contract Test is DSTest { ctrctDivideByTwo = new DivideByTwo(); ctrctUseImmutable = new UseImmutable(); ctrctRequireNeZero = new RequireNeZero(); + ctrctPayableFunctions = new PayableFunctions(); } + function testPayableFunctions() public { + ctrctPayableFunctions.payableFunctions(); + } function testRequireNeZero() public { ctrctRequireNeZero.requireNeZero(4); } From c4cfffba1eb93866dd048ef1430f20d0629bf6dd Mon Sep 17 00:00:00 2001 From: ZeroEkkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Wed, 16 Feb 2022 14:02:23 +0100 Subject: [PATCH 16/21] fix: format code in markdown --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3da4a05..da2880c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A list of common Solidity optimization tips and myths. # Tips - - - - -### Use UINT instead of BOOL for reentrancy guard ### +### Use `uint` instead of `bool` for reentrancy guard ### ```solidity /// 🤦 Unoptimized (gas: 22202) @@ -28,7 +28,7 @@ modifier nonReentrant() { } ``` -Use a reentrancy guard like [Solmate](https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol) which employs uint instead of boolean storage variable which saves gas. +Use a reentrancy guard like [Solmate](https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol) which employs `uint` instead of `bool` storage variable which saves gas. - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/Reentrancy.sol) - - - - @@ -44,7 +44,7 @@ for (uint256 index; index < arr.length; ++index) {} uint256 arrLength = arr.length; for (uint256 index; index < arrLength; ++index) {} ``` -Caching the array length first saves an SLOAD on each iteration of the loop. +Caching the array length first saves an `SLOAD` on each iteration of the loop. - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/CacheArrLength.sol) - - - - @@ -65,12 +65,12 @@ function _uncheckedIncrement(uint256 counter) private pure returns(uint256) { ... for (uint256 index; index < arrLength; index = _uncheckedIncrement(index)) {} ``` -It is a logical impossibility for index to overflow if it is always less than another integer (index < arrLength). Skipping the unchecked saves ~60 gas per iteration. Note: Part of the savings here comes from the fact that as of Solidity 0.8.2, the compiler will inline this function automatically. Using an older pragma would reduce the gas savings. For additional info see [hrkrshnn's writeup](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc) +It is a logical impossibility for index to overflow if it is always less than another integer (`index < arrLength`). Skipping the unchecked saves ~60 gas per iteration. Note: Part of the savings here comes from the fact that as of Solidity 0.8.2, the compiler will inline this function automatically. Using an older pragma would reduce the gas savings. For additional info see [hrkrshnn's writeup](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc) - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/UncheckedIncrement.sol) - - - - -### Use ++index instead of index++ to increment a loop counter ### +### Use `++index` instead of `index++` to increment a loop counter ### ```solidity /// 🤦 Unoptimized (gas: 2064) @@ -79,11 +79,11 @@ for (uint256 index; index < arrLength; index++) {} /// 🚀 Optimized (gas: 2014) for (uint256 index; index < arrLength; ++index) {} ``` -Due to reduced stack operations, using ++index saves 5 gas per iteration +Due to reduced stack operations, using `++index` saves 5 gas per iteration - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/PlusPlusIndex.sol) - - - - -### Prefer using immutable to storage ### +### Prefer using `immutable` to `storage` ### ```solidity uint256 public immutableNumber; @@ -95,11 +95,11 @@ uint256 sum = storageNumber + 1; /// 🚀 Optimized (gas: 1024) uint256 sum = immutableNumber + 1; ``` -Each storage read of the state variable is replaced by the instruction push32 value, where value is set during contract construction time. For additional info see [hrkrshnn's writeup](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc) +Each storage read of the state variable is replaced by the instruction `PUSH32` value, where value is set during contract construction time. For additional info see [hrkrshnn's writeup](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc) - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/UseImmutable.sol) - - - - -### Make functions payable ### +### Make functions `payable` ### ```solidity @@ -109,7 +109,7 @@ function doSomething() external pure {} /// 🚀 Optimized (gas: 760) function doSomething() payable external {} ``` -Making functions payable eliminates the need for an initial check of msg.value == 0 and saves 21 gas. Note: This conservatively assumes the function could be pure if not for the payable. When compared against an non-pure function the savings is more. Note: For certain contracts, adding a payable function where none existed previously could introduce a security risk. Use with caution. +Making functions `payable` eliminates the need for an initial check of `msg.value == 0` and saves 21 gas. Note: This conservatively assumes the function could be `pure` if not for the `payable`. When compared against an non-`pure` function the savings is more. Note: For certain contracts, adding a `payable` function where none existed previously could introduce a security risk. Use with caution. - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/PayableFunctions.sol) - - - - @@ -131,7 +131,7 @@ The `SHR` opcode is 3 gas cheaper than `DIV` and more imporantly, bypasses Solid - - - - -### In require(), use != 0 instead of > 0 with UINTs ### +### In `require()`, use `!= 0` instead of `> 0` with `uint` values ### ```solidity uint256 notZero = 4; @@ -142,7 +142,7 @@ require(notZero > 0); /// 🚀 Optimized (gas: 861) require(notZero != 0); ``` -In a require, when checking a UINT, using != 0 instead of > 0 saves 6 gas. Note: This only works in require but not in other situations. For more info see [this thread](https://twitter.com/transmissions11/status/1469848358558711808?s=20&t=hyTZxmZKXq06opE8wgo1aA) +In a require, when checking a `uint`, using `!= 0` instead of `> 0` saves 6 gas. Note: This only works in require but not in other situations. For more info see [this thread](https://twitter.com/transmissions11/status/1469848358558711808?s=20&t=hyTZxmZKXq06opE8wgo1aA) - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/RequireNeZero.sol) From fc0707f253235a852ca7aa20c781fe85378c7e59 Mon Sep 17 00:00:00 2001 From: ZeroEkkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Wed, 16 Feb 2022 14:32:58 +0100 Subject: [PATCH 17/21] docs: add more info to "Make functions `payable`" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da2880c..e37c209 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ function doSomething() external pure {} /// 🚀 Optimized (gas: 760) function doSomething() payable external {} ``` -Making functions `payable` eliminates the need for an initial check of `msg.value == 0` and saves 21 gas. Note: This conservatively assumes the function could be `pure` if not for the `payable`. When compared against an non-`pure` function the savings is more. Note: For certain contracts, adding a `payable` function where none existed previously could introduce a security risk. Use with caution. +Making functions `payable` eliminates the need for an initial check of `msg.value == 0` and saves 21 gas. Note: This conservatively assumes the function could be `pure` if not for the `payable`. When compared against a non-`pure` function the savings is more (24 gas). When used for a constructor, the savings is on deployment. Note: For certain contracts, adding a `payable` function where none existed previously could introduce a security risk. Use with caution. - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/PayableFunctions.sol) - - - - From da9e4de12f1f7e0690bf757fb4d90a2032985aa3 Mon Sep 17 00:00:00 2001 From: Richie Date: Wed, 16 Feb 2022 23:56:28 -0800 Subject: [PATCH 18/21] fix: add tests --- src/contracts/optimized/PayableFunctions.sol | 9 +++++++++ src/contracts/unoptimized/PayableFunctions.sol | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/contracts/optimized/PayableFunctions.sol create mode 100644 src/contracts/unoptimized/PayableFunctions.sol diff --git a/src/contracts/optimized/PayableFunctions.sol b/src/contracts/optimized/PayableFunctions.sol new file mode 100644 index 0000000..5f6eef0 --- /dev/null +++ b/src/contracts/optimized/PayableFunctions.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract PayableFunctions { + /// 🚀 Optimized + function payableFunctions() payable external { + } + +} diff --git a/src/contracts/unoptimized/PayableFunctions.sol b/src/contracts/unoptimized/PayableFunctions.sol new file mode 100644 index 0000000..083039d --- /dev/null +++ b/src/contracts/unoptimized/PayableFunctions.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract PayableFunctions { + + /// 🤦 Unoptimized + function payableFunctions() external pure { + } + +} From 964c888fcdb4700018b164cf95f100c7e1d64408 Mon Sep 17 00:00:00 2001 From: Richie Date: Thu, 17 Feb 2022 00:35:40 -0800 Subject: [PATCH 19/21] docs: update explanation --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e37c209..c3182af 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ modifier nonReentrant() { } ``` -Use a reentrancy guard like [Solmate](https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol) which employs `uint` instead of `bool` storage variable which saves gas. +Use a reentrancy guard like [Solmate](https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol) which employs `uint` instead of `bool` storage variable which saves gas. The initial `SSTORE` of _true_ in the unoptimized version costs over 20,000 gas while the second SSTORE of _false_ costs only 100. But both `SSTORE` (for 2 and 1) cost only 100 gas. - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/Reentrancy.sol) - - - - @@ -151,6 +151,5 @@ In a require, when checking a `uint`, using `!= 0` instead of `> 0` saves 6 gas. # Myths - - - - -- - - - -### Note on gas numbers: The gas usage numbers include the total gas reported by Dapp Test for running a test which calls the example function. This includes the gas overhead of the test itself. The important number here is the gas savings which is the difference between optimized and unoptimized gas usage numbers. We have done our best to reduce noise in the estimates and confirm the actual gas usage based on opcodes run. But there have still been some inconsistencies in the gas numbers between the two contracts used for testing. + From 7b32928e228ceb5e76259d5ab9042948a03bf68b Mon Sep 17 00:00:00 2001 From: Richie Date: Thu, 17 Feb 2022 01:01:18 -0800 Subject: [PATCH 20/21] feat: new tip re: array += --- README.md | 15 +++++++++++++++ src/tests/Optimized.t.sol | 7 +++++++ src/tests/Unoptimized.t.sol | 6 ++++++ 3 files changed, 28 insertions(+) diff --git a/README.md b/README.md index c3182af..57729d2 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,21 @@ function doSomething() payable external {} Making functions `payable` eliminates the need for an initial check of `msg.value == 0` and saves 21 gas. Note: This conservatively assumes the function could be `pure` if not for the `payable`. When compared against a non-`pure` function the savings is more (24 gas). When used for a constructor, the savings is on deployment. Note: For certain contracts, adding a `payable` function where none existed previously could introduce a security risk. Use with caution. - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/PayableFunctions.sol) +- - - - +### For array elements, arr[i] = arr[i] + 1 is cheaper than arr[i] += 1 ### + +```solidity +uint256[2] public arr = [uint256(1), 2]; // storage + +/// 🤦 Unoptimized (gas: 1110) +arr[0] += 1; + +/// 🚀 Optimized (gas: 1085) +arr[0] = arr[0] + 1; +``` +Due to stack operations this is 25 gas cheaper when dealing with arrays in storage, and 4 gas cheaper for memory arrays. + - [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/ArrayPlus.sol) + - - - - ### When dividing by two, use `>> 1` instead of `/ 2` ### diff --git a/src/tests/Optimized.t.sol b/src/tests/Optimized.t.sol index 92d275d..acfa58e 100644 --- a/src/tests/Optimized.t.sol +++ b/src/tests/Optimized.t.sol @@ -10,6 +10,7 @@ import "./../contracts/optimized/PlusPlusIndex.sol"; import "./../contracts/optimized/UncheckedIncrement.sol"; import "./../contracts/optimized/UseImmutable.sol"; import "./../contracts/optimized/RequireNeZero.sol"; +import "./../contracts/optimized/ArrayPlus.sol"; import "./../contracts/optimized/PayableFunctions.sol"; contract Test is DSTest { @@ -21,6 +22,7 @@ contract Test is DSTest { RequireNeZero public ctrctRequireNeZero; DivideByTwo public ctrctDivideByTwo; PayableFunctions public ctrctPayableFunctions; + ArrayPlus public ctrctArrayPlus; function setUp() public { ctrctReentrancy = new Reentrancy(); @@ -31,6 +33,11 @@ contract Test is DSTest { ctrctUncheckedIncrement = new UncheckedIncrement(); ctrctDivideByTwo = new DivideByTwo(); ctrctPayableFunctions = new PayableFunctions(); + ctrctArrayPlus = new ArrayPlus(); + } + + function testArrayPlus() public { + ctrctArrayPlus.arrayPlus(); } function testPayableFunctions() public { diff --git a/src/tests/Unoptimized.t.sol b/src/tests/Unoptimized.t.sol index f6f53d1..ba4552d 100644 --- a/src/tests/Unoptimized.t.sol +++ b/src/tests/Unoptimized.t.sol @@ -11,6 +11,7 @@ import "./../contracts/unoptimized/UncheckedIncrement.sol"; import "./../contracts/unoptimized/UseImmutable.sol"; import "./../contracts/unoptimized/RequireNeZero.sol"; import "./../contracts/unoptimized/PayableFunctions.sol"; +import "./../contracts/unoptimized/ArrayPlus.sol"; contract Test is DSTest { CacheArrLength public ctrctCacheArrLength; @@ -21,6 +22,7 @@ contract Test is DSTest { UseImmutable public ctrctUseImmutable; RequireNeZero public ctrctRequireNeZero; PayableFunctions public ctrctPayableFunctions; + ArrayPlus public ctrctArrayPlus; function setUp() public { ctrctCacheArrLength = new CacheArrLength(); @@ -31,8 +33,12 @@ contract Test is DSTest { ctrctUseImmutable = new UseImmutable(); ctrctRequireNeZero = new RequireNeZero(); ctrctPayableFunctions = new PayableFunctions(); + ctrctArrayPlus = new ArrayPlus(); } + function testArrayPlus() public { + ctrctArrayPlus.arrayPlus(); + } function testPayableFunctions() public { ctrctPayableFunctions.payableFunctions(); } From 6bb2a4a5ad93060648c6dc3a64fcdf1c2c93b11b Mon Sep 17 00:00:00 2001 From: Richie Date: Thu, 17 Feb 2022 08:34:25 -0800 Subject: [PATCH 21/21] feat: ArayPlus contracts --- src/contracts/optimized/ArrayPlus.sol | 12 ++++++++++++ src/contracts/optimized/Reentrancy.sol | 1 + src/contracts/unoptimized/ArrayPlus.sol | 12 ++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 src/contracts/optimized/ArrayPlus.sol create mode 100644 src/contracts/unoptimized/ArrayPlus.sol diff --git a/src/contracts/optimized/ArrayPlus.sol b/src/contracts/optimized/ArrayPlus.sol new file mode 100644 index 0000000..41cae35 --- /dev/null +++ b/src/contracts/optimized/ArrayPlus.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract ArrayPlus { + uint256[2] public arr = [uint256(1), 2]; + + function arrayPlus() external { + /// 🚀 Optimized + arr[0] = arr[0] + 4; + } + +} diff --git a/src/contracts/optimized/Reentrancy.sol b/src/contracts/optimized/Reentrancy.sol index 5e8430d..a98b760 100644 --- a/src/contracts/optimized/Reentrancy.sol +++ b/src/contracts/optimized/Reentrancy.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.11; contract Reentrancy { event GenericEvent(); + uint256 private locked = 1; modifier nonReentrant() { diff --git a/src/contracts/unoptimized/ArrayPlus.sol b/src/contracts/unoptimized/ArrayPlus.sol new file mode 100644 index 0000000..fa8c9b2 --- /dev/null +++ b/src/contracts/unoptimized/ArrayPlus.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.11; + +contract ArrayPlus { + uint256[2] public arr = [uint256(1), 2]; + + function arrayPlus() external { + /// 🚀 Optimized + arr[0] += 4; + } + +}