Yieldy contest - _Adam's results

A protocol for gaining single side yields on various tokens.

General Information

Platform: Code4rena

Start Date: 21/06/2022

Pot Size: $50,000 USDC

Total HM: 31

Participants: 99

Period: 5 days

Judges: moose-code, JasoonS, denhampreen

Total Solo HM: 17

Id: 139

League: ETH

Yieldy

Findings Distribution

Researcher Performance

Rank: 43/99

Findings: 2

Award: $113.12

🌟 Selected for report: 0

🚀 Solo Findings: 0

[N01] Duplicate Code

The same code is repeated twice, recommend either removing line 84 - 87 or Line 88 - 91. Staking.sol#L84-L91

[N02] Incomplete Natspec

BatchRequests.sol#L63 - missing @param _ index

[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 ADMIN_ROLE (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:

  • Cheaper external calls if return values exist due to skipping existance checks. (0.8.10)

Staking.sol#L2 Yieldy.sol#L2 Migration.sol#L2 LiquidityReserve.sol#L2 BatchRequests.sol#L2

#0 - moose-code

2022-07-09T12:37:53Z

Good report!

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter