The following sections detail the gas optimizations found throughout the codebase. Each optimization is documented with the setup, an explainer for the optimization, a gas report and line identifiers for each optimization across the codebase. For each section's gas report, the optimizer was turned on and set to 10000 runs. You can replicate any tests/gas reports by heading to 0xKitsune/gas-lab and cloning the repo. Then, simply copy/paste the contract examples from any section and run forge test --gas-report. You can also easily update the optimizer runs in the foundry.toml.
Use assembly to check for address(0)
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
}
function testGas() public view {
c0.ownerNotZero(address(this));
c1.assemblyOwnerNotZero(address(this));
}
}
contract Contract0 {
function ownerNotZero(address _addr) public pure {
require(_addr != address(0), "zero address)");
}
}
contract Contract1 {
function assemblyOwnerNotZero(address _addr) public pure {
assembly {
if iszero(_addr) {
mstore(0x00, "zero address")
revert(0x00, 0x20)
}
}
}
}
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
}
function testGas() public view {
c0.solidityHash(2309349, 2304923409);
c1.assemblyHash(2309349, 2304923409);
}
}
contract Contract0 {
function solidityHash(uint256 a, uint256 b) public view {
//unoptimized
keccak256(abi.encodePacked(a, b));
}
}
contract Contract1 {
function assemblyHash(uint256 a, uint256 b) public view {
//optimized
assembly {
mstore(0x00, a)
mstore(0x20, b)
let hashedVal := keccak256(0x00, 0x40)
}
}
}
Use assembly when getting a contract's balance of ETH.
You can use selfbalance() instead of address(this).balance when getting your contract's balance of ETH to save gas. Additionally, you can use balance(address) instead of address.balance() when getting an external contract's balance of ETH.
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
Contract2 c2;
Contract3 c3;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
c2 = new Contract2();
c3 = new Contract3();
}
function testGas() public {
c0.addressInternalBalance();
c1.assemblyInternalBalance();
c2.addressExternalBalance(address(this));
c3.assemblyExternalBalance(address(this));
}
}
contract Contract0 {
function addressInternalBalance() public returns (uint256) {
return address(this).balance;
}
}
contract Contract1 {
function assemblyInternalBalance() public returns (uint256) {
assembly {
let c := selfbalance()
mstore(0x00, c)
return(0x00, 0x20)
}
}
}
contract Contract2 {
function addressExternalBalance(address addr) public {
uint256 bal = address(addr).balance;
bal++;
}
}
contract Contract3 {
function assemblyExternalBalance(address addr) public {
uint256 bal;
assembly {
bal := balance(addr)
}
bal++;
}
}
unchecked{++i} instead of i++ (or use assembly when applicable)
Use ++i instead of i++. This is especially useful in for loops but this optimization can be used anywhere in your code. You can also use unchecked{++i;} for even more gas savings but this will not check to see if i overflows. For extra safety if you are worried about this, you can add a require statement after the loop checking if i is equal to the final incremented value. For best gas savings, use inline assembly, however this limits the functionality you can achieve. For example you cant use Solidity syntax to internally call your own contract within an assembly block and external calls must be done with the call() or delegatecall() instruction. However when applicable, inline assembly will save much more gas.
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
Contract2 c2;
Contract3 c3;
Contract4 c4;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
c2 = new Contract2();
c3 = new Contract3();
c4 = new Contract4();
}
function testGas() public {
c0.iPlusPlus();
c1.plusPlusI();
c2.uncheckedPlusPlusI();
c3.safeUncheckedPlusPlusI();
c4.inlineAssemblyLoop();
}
}
contract Contract0 {
//loop with i++
function iPlusPlus() public pure {
uint256 j = 0;
for (uint256 i; i < 10; i++) {
j++;
}
}
}
contract Contract1 {
//loop with ++i
function plusPlusI() public pure {
uint256 j = 0;
for (uint256 i; i < 10; ++i) {
j++;
}
}
}
contract Contract2 {
//loop with unchecked{++i}
function uncheckedPlusPlusI() public pure {
uint256 j = 0;
for (uint256 i; i < 10; ) {
j++;
unchecked {
++i;
}
}
}
}
contract Contract3 {
//loop with unchecked{++i} with additional overflow check
function safeUncheckedPlusPlusI() public pure {
uint256 j = 0;
uint256 i = 0;
for (i; i < 10; ) {
j++;
unchecked {
++i;
}
}
//check for overflow
assembly {
if lt(i, 10) {
mstore(0x00, "loop overflow")
revert(0x00, 0x20)
}
}
}
}
contract Contract4 {
//loop with inline assembly
function inlineAssemblyLoop() public pure {
assembly {
let j := 0
for {
let i := 0
} lt(i, 10) {
i := add(i, 0x01)
} {
j := add(j, 0x01)
}
}
}
}
Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety. If using Solidity versions < 0.8.0 and you are using Safemath, you can gain significant gas savings by using assembly to calculate values and checking for overflow/underflow.
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
Contract2 c2;
Contract3 c3;
Contract4 c4;
Contract5 c5;
Contract6 c6;
Contract7 c7;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
c2 = new Contract2();
c3 = new Contract3();
c4 = new Contract4();
c5 = new Contract5();
c6 = new Contract6();
c7 = new Contract7();
}
function testGas() public {
c0.addTest(34598345, 100);
c1.addAssemblyTest(34598345, 100);
c2.subTest(34598345, 100);
c3.subAssemblyTest(34598345, 100);
c4.mulTest(34598345, 100);
c5.mulAssemblyTest(34598345, 100);
c6.divTest(34598345, 100);
c7.divAssemblyTest(34598345, 100);
}
}
contract Contract0 {
//addition in Solidity
function addTest(uint256 a, uint256 b) public pure {
uint256 c = a + b;
}
}
contract Contract1 {
//addition in assembly
function addAssemblyTest(uint256 a, uint256 b) public pure {
assembly {
let c := add(a, b)
if lt(c, a) {
mstore(0x00, "overflow")
revert(0x00, 0x20)
}
}
}
}
contract Contract2 {
//subtraction in Solidity
function subTest(uint256 a, uint256 b) public pure {
uint256 c = a - b;
}
}
contract Contract3 {
//subtraction in assembly
function subAssemblyTest(uint256 a, uint256 b) public pure {
assembly {
let c := sub(a, b)
if gt(c, a) {
mstore(0x00, "underflow")
revert(0x00, 0x20)
}
}
}
}
contract Contract4 {
//multiplication in Solidity
function mulTest(uint256 a, uint256 b) public pure {
uint256 c = a * b;
}
}
contract Contract5 {
//multiplication in assembly
function mulAssemblyTest(uint256 a, uint256 b) public pure {
assembly {
let c := mul(a, b)
if lt(c, a) {
mstore(0x00, "overflow")
revert(0x00, 0x20)
}
}
}
}
contract Contract6 {
//division in Solidity
function divTest(uint256 a, uint256 b) public pure {
uint256 c = a * b;
}
}
contract Contract7 {
//division in assembly
function divAssemblyTest(uint256 a, uint256 b) public pure {
assembly {
let c := div(a, b)
if gt(c, a) {
mstore(0x00, "underflow")
revert(0x00, 0x20)
}
}
}
}
Gas Optimizations
The following sections detail the gas optimizations found throughout the codebase. Each optimization is documented with the setup, an explainer for the optimization, a gas report and line identifiers for each optimization across the codebase. For each section's gas report, the optimizer was turned on and set to 10000 runs. You can replicate any tests/gas reports by heading to 0xKitsune/gas-lab and cloning the repo. Then, simply copy/paste the contract examples from any section and run
forge test --gas-report
. You can also easily update the optimizer runs in thefoundry.toml
.Use assembly to check for address(0)
Gas Report
Lines
wfCashLogic.sol:211
DebtIssuanceModule.sol:220
DebtIssuanceModule.sol:643
Use assembly to hash instead of Solidity
Gas Report
Lines
Use assembly when getting a contract's balance of ETH.
You can use
selfbalance()
instead ofaddress(this).balance
when getting your contract's balance of ETH to save gas. Additionally, you can usebalance(address)
instead ofaddress.balance()
when getting an external contract's balance of ETH.Gas Report
Lines
wfCashLogic.sol:59
wfCashLogic.sol:267
wfCashLogic.sol:282
wfCashLogic.sol:304
unchecked{++i}
instead ofi++
(or use assembly when applicable)Use
++i
instead ofi++
. This is especially useful in for loops but this optimization can be used anywhere in your code. You can also useunchecked{++i;}
for even more gas savings but this will not check to see ifi
overflows. For extra safety if you are worried about this, you can add a require statement after the loop checking ifi
is equal to the final incremented value. For best gas savings, use inline assembly, however this limits the functionality you can achieve. For example you cant use Solidity syntax to internally call your own contract within an assembly block and external calls must be done with thecall()
ordelegatecall()
instruction. However when applicable, inline assembly will save much more gas.Gas Report
Lines
SetToken.sol:176
SetToken.sol:181
SetToken.sol:485
SetToken.sol:498
SetToken.sol:502
SetToken.sol:513
SetToken.sol:527
SetToken.sol:604
SetToken.sol:614
SetToken.sol:636
SetToken.sol:641
NotionalTradeModule.sol:267
NotionalTradeModule.sol:291
NotionalTradeModule.sol:455
NotionalTradeModule.sol:701
NotionalTradeModule.sol:706
NotionalTradeModule.sol:714
NotionalTradeModule.sol:719
DebtIssuanceModule.sol:471
DebtIssuanceModule.sol:508
DebtIssuanceModule.sol:515
DebtIssuanceModule.sol:550
DebtIssuanceModule.sol:590
DebtIssuanceModule.sol:656
DebtIssuanceModule.sol:666
DebtIssuanceModule.sol:689
DebtIssuanceModule.sol:693
Use multiple
require()
statments insted ofrequire(expression && expression && ...)
Gas Report
Lines
wfCashLogic.sol:116
wfCashLogic.sol:129
Use assembly for math (add, sub, mul, div)
Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety. If using Solidity versions < 0.8.0 and you are using Safemath, you can gain significant gas savings by using assembly to calculate values and checking for overflow/underflow.
Gas Report
Lines
SetToken.sol:529
SetToken.sol:647
SetToken.sol:602
SetToken.sol:660
wfCashLogic.sol:228
wfCashLogic.sol:305
wfCashERC4626.sol:58
wfCashERC4626.sol:85
wfCashERC4626.sol:89
wfCashERC4626.sol:105
wfCashERC4626.sol:300
DebtIssuanceModule.sol:528