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

feat: adds a few more tips #1

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3afa909
✨ Add a few more tips
devtooligan Dec 14, 2021
0d796cb
docs: update descriptions and formatting
devtooligan Dec 15, 2021
5b1cad0
dapp init GolfCourse
devtooligan Dec 18, 2021
244182e
dapp install ds-test
devtooligan Dec 18, 2021
fc1882b
feat: adds examples and tests
devtooligan Dec 18, 2021
889b45f
docs: update readme.md with links and gas
devtooligan Dec 18, 2021
fb1f401
feat: work on formatting and add a bunch of examples
devtooligan Feb 13, 2022
48b3b6e
fix: typo
devtooligan Feb 14, 2022
d33debe
feat: update comments
devtooligan Feb 16, 2022
b814df2
feat: add RequireNeZero tip
devtooligan Feb 16, 2022
f45ed35
tweak: update gitignore
devtooligan Feb 16, 2022
70d07b7
fix: update directory structure to eliminate differences in contract …
devtooligan Feb 16, 2022
5606811
fix: update gas usage from new ctrct names
devtooligan Feb 16, 2022
52b02b0
fix: update links in readme
devtooligan Feb 16, 2022
00d2a4f
feat: add payable functions tip
devtooligan Feb 16, 2022
c4cfffb
fix: format code in markdown
ZeroEkkusu Feb 16, 2022
fc0707f
docs: add more info to "Make functions `payable`"
ZeroEkkusu Feb 16, 2022
d4dabe3
Merge pull request #4 from ZeroEkkusu/work-1
devtooligan Feb 16, 2022
da9e4de
fix: add tests
devtooligan Feb 17, 2022
bfac3ce
Merge branch 'some-tips-to-start' of github.com:Rari-Capital/golf-cou…
devtooligan Feb 17, 2022
964c888
docs: update explanation
devtooligan Feb 17, 2022
7b32928
feat: new tip re: array +=
devtooligan Feb 17, 2022
6bb2a4a
feat: ArayPlus contracts
devtooligan Feb 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .dapprc
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.sol linguist-language=Solidity
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/out
cache/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lib/ds-test"]
path = lib/ds-test
url = https://github.com/dapphub/ds-test
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
all :; dapp build
clean :; dapp clean
test :; dapp test
deploy :; dapp create GolfCourse
169 changes: 159 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,170 @@
# golf-course
A list of common Solidity optimization tips and myths.

## Tips

### Right Shift Instead of Dividing By 2
# Tips

The `SHR` opcode is 3 gas cheaper than `DIV` and also bypasses Solidity's division by 0 prevention overhead.
- - - -
### Use `uint` instead of `bool` for reentrancy guard ###

- [Gas Usage]()
- [Full Example]()
```solidity
/// 🤦 Unoptimized (gas: 22202)

bool private locked = false;
modifier nonReentrant() {
require(locked == false, "REENTRANCY");
locked = true;
_;
locked = false;
}

/// 🚀 Optimized (gas: 2125)
bool private locked = 1;
modifier nonReentrant() {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
```

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)

- - - -
### When iterating through a storage array, cache the array length first ###

```solidity
// Unoptimized:
uint256 two = 4 / 2;
uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Optimized:
uint256 two = 4 >> 1;
/// 🤦 Unoptimized (gas: 3055)
for (uint256 index; index < arr.length; ++index) {}

/// 🚀 Optimized (gas: 2013)
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/optimized/CacheArrLength.sol)

- - - -
### Use unchecked with counter incrementing logic ###

```solidity

/// 🤦 Unoptimized (gas: 2035)
for (uint256 index; index < arrLength; ++index) {}


/// 🚀 Optimized (gas: 1375)
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/optimized/UncheckedIncrement.sol)


- - - -
### Use `++index` instead of `index++` to increment a loop counter ###

```solidity
/// 🤦 Unoptimized (gas: 2064)
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
- [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/PlusPlusIndex.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/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 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` ###

```solidity
uint256 four = 4;
uint256 two;

/// 🤦 Unoptimized (gas: 1012)
two = four / 2;

/// 🚀 Optimized (gas: 933)
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/optimized/DivideByTwo.sol)


- - - -
### In `require()`, use `!= 0` instead of `> 0` with `uint` values ###

```solidity
uint256 notZero = 4;

/// 🤦 Unoptimized (gas: 867)
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)
- [Full Example](https://github.com/Rari-Capital/golf-course/blob/fc1882bacfec50787d9e9435d59fed4a9091fb21/src/optimized/RequireNeZero.sol)




# Myths
- - - -



## Myths
1 change: 1 addition & 0 deletions lib/ds-test
Submodule ds-test added at 0a5da5
12 changes: 12 additions & 0 deletions src/contracts/optimized/ArrayPlus.sol
Original file line number Diff line number Diff line change
@@ -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;
}

}
14 changes: 14 additions & 0 deletions src/contracts/optimized/CacheArrLength.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract CacheArrLength {
uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10];

function cacheArrLength() external view {
uint256 arrLength = arr.length;

/// 🚀 Optimized
for (uint256 index; index < arrLength; ++index) {}
}

}
10 changes: 10 additions & 0 deletions src/contracts/optimized/DivideByTwo.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract DivideByTwo {
function divideByTwo(uint256 four) external pure returns (uint256 two) {
/// 🚀 Optimized
two = four >> 1;
}

}
9 changes: 9 additions & 0 deletions src/contracts/optimized/PayableFunctions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract PayableFunctions {
/// 🚀 Optimized
function payableFunctions() payable external {
}

}
14 changes: 14 additions & 0 deletions src/contracts/optimized/PlusPlusIndex.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract PlusPlusIndex {
uint256[] public arr = [uint256(1), 2, 3, 4, 5, 6, 7, 8, 9, 10];

function plusPlusIndex() external view {
uint256 arrLength = arr.length;

/// 🚀 Optimized
for (uint256 index; index < arrLength; ++index) {}
}

}
24 changes: 24 additions & 0 deletions src/contracts/optimized/Reentrancy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract Reentrancy {
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();
}
}
10 changes: 10 additions & 0 deletions src/contracts/optimized/RequireNeZero.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract RequireNeZero {
function requireNeZero(uint256 notZero) external pure {
/// 🚀 Optimized
require(notZero != 0);
}

}
19 changes: 19 additions & 0 deletions src/contracts/optimized/UncheckedIncrement.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract UncheckedIncrement {
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;

/// 🚀 Optimized
for (uint256 index; index < arrLength; index = _uncheckedIncrement(index)) {}
}

}
17 changes: 17 additions & 0 deletions src/contracts/optimized/UseImmutable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract UseImmutable {
uint256 public immutableNumber;
uint256 public storageNumber;

constructor() {
immutableNumber = 1;
storageNumber = 1;
}
function useImmutable() external view returns(uint256) {
/// 🚀 Optimized
return immutableNumber + 1;
}

}
12 changes: 12 additions & 0 deletions src/contracts/unoptimized/ArrayPlus.sol
Original file line number Diff line number Diff line change
@@ -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;
}

}
13 changes: 13 additions & 0 deletions src/contracts/unoptimized/CacheArrLength.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract CacheArrLength {
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) {}
}

}
11 changes: 11 additions & 0 deletions src/contracts/unoptimized/DivideByTwo.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract DivideByTwo {

function divideByTwo(uint256 four) external pure returns (uint256 two) {
/// 🤦 Unoptimized
two = four / 2;
}

}
10 changes: 10 additions & 0 deletions src/contracts/unoptimized/PayableFunctions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.11;

contract PayableFunctions {

/// 🤦 Unoptimized
function payableFunctions() external pure {
}

}
Loading