[G01] Function argument require statements should come first
Require checks that involve function arguments/constants should come before any that involve state variables. That way if they fail you are saving having to load any storage variables (100 gas)
As your using a solidity version > 0.8.4 you can replace revert strings with custom errors. This will save in deployment costs and runtime costs.
Based on a test in remix, replacing a single revert string with a custom error saved 12,404 gas in deployment cost and 86 gas on each function call.
contract Test {
uint256 a;
function check() external {
require(a != 0, "check failed");
}
} (Deployment cost: 114,703, Cost on Function call: 23,392)
vs
contract Test {
uint256 a;
error checkFailed();
function check() external {
if (a != 0) revert checkFailed();
}
} (Deployment cost: 102,299, Cost on Function call: 23,306)
If optimising for runtime costs over deployment costs you can seperate && in require functions into 2 parts. I ran a basic test in remix and it cost an extra 234 gas to deploy but will save ~9 gas everytime the require function is called.
contract Test {
uint256 a = 0;
uint256 b = 1;
function test() external {
require(a == 0 && b > a)
(Deployment cost: 123,291, Cost on function call: 29,371)
vs
require(a == 0);
require(b > a);
(Deployment cost: 123,525, Cost on function call: 29,362)
}
}
When checking whether a uint is > 0 in a require statement (with optimiser on) you can save a small amount of gas by replacing with != 0.
I ran a test in remix and found the savings for a single occurance is 632 in deployment cost and 6 gas on each function call.
contract Test {
uint256 a;
function check() external {
require(a > 0);
(Deployment cost: 79,763, Cost on function call: 23,305)
vs
require(a != 0);
(Deployment cost: 79,331, Cost on function call: 23,299)
}
}
Based on test in remix you can save ~1,007 gas on deployment and ~15 gas on execution cost if you use x = x + y over x += y (Is only true for Storage Variables).
contract Test {
uint256 x = 1;
function test() external {
x += 3;
(Deployment Cost: 153,124, Execution Cost: 30,369)
vs
x = x + 1;
(Deployment Cost: 152,117, Execution Cost: 30,354)
}
}
You can save reading from storage (100 gas for an SLOAD) by emiting local variables over storage variables when they have the same value.
Staking.sol#L649 - can change to: emit LogSetCurvePool(CURVE_POOL, to, from);
[G07] Caching Storage Variables
Whenever referencing a storage variable more than once in a function without modifying you can save ~97 gas per use by caching the value. (normally 100 gas each use vs 103 gas to SLOAD/MSTORE for the first use and then only 3 gas for further uses)
Staking.sol#L78-L92 - can use the function arguments instead of CURVE_POOL, TOKE_POOL, STAKING_TOKEN, YIELDY_TOKEN, LIQUIDITY_RESERVE & TOKE_TOKEN as they have the same values. Will save 10 SLOAD's (save 1,000 gas)
Staking.sol#L144-L147 - can cache TOKE_TOKEN (save 97 gas)
Staking.sol#L289-L290 - can cache withdrawalAmount (save 97 gas)
Staking.sol#L320-L321 - can cache TOKE_POOL (save 97 gas)
Staking.sol#L392-L393 - can cache requestWithdrawalAmount (save 97 gas)
Staking.sol#L412-L446 - can cache YIELDY_TOKEN (save up to 196 gas)
Staking.sol#L471-L473 - can cache YIELDY_TOKEN (save 97 gas)
Staking.sol#L747-L755 - can cache STAKING_TOKEN (save 97 gas)
Yieldy.sol#L38-L41 - can cache ADMINROLE (save 388 gas) or can set a variable to keccak256("ADMIN") and using that.
Yieldy.sol#L83 - can replace totalSupply with currentTotalSupply as it is already cached (save 100 gas)
Yieldy.sol#L122-L129 can cache _ totalSupply (save 97 gas)
[G08] Public Function that can be External
The following function is never called in their contracts and can be switched to external to save gas:
Functions that will always revert when regular users call them such as those with onlyOwner modifier can be marked payable to save a small amount of gas (~24 Gas when function is called based on remix test)
[G01] Function argument require statements should come first
Require checks that involve function arguments/constants should come before any that involve state variables. That way if they fail you are saving having to load any storage variables (100 gas)
Staking.sol#L408-L410 - amount > 0 check should be first Yieldy.sol#L58-L59 - stakingContract != address(0) check should be first LiquidityReserve.sol#L61-L62 - _ stakingContract != address(0) check should be first
[G02] Custom Errors
As your using a solidity version > 0.8.4 you can replace revert strings with custom errors. This will save in deployment costs and runtime costs. Based on a test in remix, replacing a single revert string with a custom error saved 12,404 gas in deployment cost and 86 gas on each function call.
Revert Strings that can be updated to custom errors: Staking.sol#L54 Staking.sol#L118 Staking.sol#L143 Staking.sol#L408-L410 Staking.sol#L527 Staking.sol#L572-L574 Staking.sol#L586 Staking.sol#L604-L605 Staking.sol#L611 Staking.sol#L644 Staking.sol#L676 Yieldy.sol#L58-L59 Yieldy.sol#L83 Yieldy.sol#L96 Yieldy.sol#L187 Yieldy.sol#L190 Yieldy.sol#L210 Yieldy.sol#L249 Yieldy.sol#L257 Yieldy.sol#L279 Yieldy.sol#L286 Migration.sol#L20 LiquidityReserve.sol#L25 LiquidityReserve.sol#L44 LiquidityReserve.sol#L61-L62 LiquidityReserve.sol#L68 LiquidityReserve.sol#L94 LiquidityReserve.sol#L105 LiquidityReserve.sol#L163 LiquidityReserve.sol#L170 LiquidityReserve.sol#L192 LiquidityReserve.sol#L215
[G03] && in Require Statements
If optimising for runtime costs over deployment costs you can seperate && in require functions into 2 parts. I ran a basic test in remix and it cost an extra 234 gas to deploy but will save ~9 gas everytime the require function is called.
Require statements that can be split up: Staking.sol#L575 Staking.sol#L605-L609 Staking.sol#L611-L614
[G04] Uint > 0 Checks in Require Statements
When checking whether a uint is > 0 in a require statement (with optimiser on) you can save a small amount of gas by replacing with != 0. I ran a test in remix and found the savings for a single occurance is 632 in deployment cost and 6 gas on each function call.
Instances where uint != 0 can be used: Staking.sol#L118 Staking.sol#L410 Staking.sol#L572 Staking.sol#L604 Yieldy.sol#L83 Yieldy.sol#L96
[G05] x = x + y is Cheaper than x += y
Based on test in remix you can save ~1,007 gas on deployment and ~15 gas on execution cost if you use x = x + y over x += y (Is only true for Storage Variables).
Instances where x = x + y/ x = x - y can be used: Staking.sol#L309-L310 Staking.sol#L494 Staking.sol#L694
[G06] Emitting Storage Variables
You can save reading from storage (100 gas for an SLOAD) by emiting local variables over storage variables when they have the same value.
Staking.sol#L649 - can change to: emit LogSetCurvePool(CURVE_POOL, to, from);
[G07] Caching Storage Variables
Whenever referencing a storage variable more than once in a function without modifying you can save ~97 gas per use by caching the value. (normally 100 gas each use vs 103 gas to SLOAD/MSTORE for the first use and then only 3 gas for further uses)
Staking.sol#L78-L92 - can use the function arguments instead of CURVE_POOL, TOKE_POOL, STAKING_TOKEN, YIELDY_TOKEN, LIQUIDITY_RESERVE & TOKE_TOKEN as they have the same values. Will save 10 SLOAD's (save 1,000 gas) Staking.sol#L144-L147 - can cache TOKE_TOKEN (save 97 gas) Staking.sol#L289-L290 - can cache withdrawalAmount (save 97 gas) Staking.sol#L320-L321 - can cache TOKE_POOL (save 97 gas) Staking.sol#L392-L393 - can cache requestWithdrawalAmount (save 97 gas) Staking.sol#L412-L446 - can cache YIELDY_TOKEN (save up to 196 gas) Staking.sol#L471-L473 - can cache YIELDY_TOKEN (save 97 gas) Staking.sol#L747-L755 - can cache STAKING_TOKEN (save 97 gas) Yieldy.sol#L38-L41 - can cache ADMINROLE (save 388 gas) or can set a variable to keccak256("ADMIN") and using that. Yieldy.sol#L83 - can replace totalSupply with currentTotalSupply as it is already cached (save 100 gas) Yieldy.sol#L122-L129 can cache _ totalSupply (save 97 gas)
[G08] Public Function that can be External
The following function is never called in their contracts and can be switched to external to save gas:
Staking.sol#L370 - unstakeAllFromTokemak()
[G09] Marking Restricted Functions Payable
Functions that will always revert when regular users call them such as those with onlyOwner modifier can be marked payable to save a small amount of gas (~24 Gas when function is called based on remix test)
Staking.sol#L141 Staking.sol#L157 Staking.sol#L167 Staking.sol#L177 Staking.sol#L187 Staking.sol#L197 Staking.sol#L207 Staking.sol#L217 Staking.sol#L226 Staking.sol#L235 Staking.sol#L246-L248 Staking.sol#L370 Staking.sol#L769 Yieldy.sol#L54-L56 Yieldy.sol#L78-L80 LiquidityReserve.sol#L59 LiquidityReserve.sol#L92 BatchRequests.sol#L81 BatchRequests.sol#L89
[G10] Recommend an Updated Solidity Version
Updating to at least solidity version 0.8.10 allows contracts to take advantage of updated gas saving benefits such as:
Staking.sol#L2 Yieldy.sol#L2 Migration.sol#L2 LiquidityReserve.sol#L2 BatchRequests.sol#L2