code-423n4 / 2022-06-yieldy-findings

0 stars 0 forks source link

Gas Optimizations #100

Open code423n4 opened 2 years ago

code423n4 commented 2 years ago

[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.

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)

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.

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

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

Require statements that can be split up: 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.

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)    
    }
}

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).

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)
    }

}

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

moose-code commented 2 years ago

Good report!