Lybra Finance - ReyAdmirado's results

A protocol building the first interest-bearing omnichain stablecoin backed by LSD.

General Information

Platform: Code4rena

Start Date: 23/06/2023

Pot Size: $60,500 USDC

Total HM: 31

Participants: 132

Period: 10 days

Judge: 0xean

Total Solo HM: 10

Id: 254

League: ETH

Lybra Finance

Findings Distribution

Researcher Performance

Rank: 69/132

Findings: 1

Award: $80.43

Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

80.434 USDC - $80.43

Labels

bug
G (Gas Optimization)
grade-a
high quality report
sponsor acknowledged
G-09

External Links

1. state variables can be packed into fewer storage slots

If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables are also cheaper.

stableToken and premiumTradingEnabled can be put together into one slot reducing slots used by 1 and also they are accessed in one function.

isEUSDBuyoutAllowed and esLBR can be put together into one slot reducing slots used by 1 and also they are accessed in one function.

2. expressions for constant values such as a call to keccak256(), should use immutable rather than constant

3. state variables should be cached in stack variables rather than re-reading them from storage (ones not found in bot audit)

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

in the modifier here rewardPerToken() is being called to get rewardPerTokenStored but first we can cache what the function returns and use that cached version to assign rewardPerTokenStored #L57 and userRewardPerTokenPaid[_account] #L62. saves 100 gas per call of modifier --> 400 gas save as it is called 4 times

in the modifier here rewardPerToken() is being called to get rewardPerTokenStored but first we can cache what the function returns and use that cached version to assign rewardPerTokenStored #L73 and userRewardPerTokenPaid[_account] #L78. saves 100 gas per call of modifier --> 400 gas save as it is called 4 times

duration is being read twice(once in one of if or else then in #L239) cache it before the if statement to save 100 gas

depositedAsset[onBehalfOf] is being read twice, once in #L156 and #L159 so it should be cached before to reduce one complex SLOAD

depositedAsset[onBehalfOf] is being read twice, once in #L190 and #L192 so it should be cached before to reduce one complex SLOAD

borrowed[provider] will be read twice if the require in #L234 doesnt revert so we can cache before the require to reduce one complex SLOAD

depositedAsset[onBehalfOf] is being read twice, once in #L127 and #L130 so it should be cached before to reduce one complex SLOAD

borrowed[provider] will be read twice if the require in #L159 doesnt revert so we can cache before the require to reduce one complex SLOAD

if block.timestamp >= finishAt isnt true finishAt will be read twice and we can save 97 gas if we cache it before the if statement with just risking 3 gas loss if the condition turns out true

if time2fullRedemption[msg.sender] > block.timestamp condition be true time2fullRedemption[msg.sender] is gonna be read twice so we can cache if before the if statement and use that instead to reduce one complex SLOAD. saves lots of gas by risking losing 3 if the condition is false.

if time2fullRedemption[user] > lastWithdrawTime[user] condition be true lastWithdrawTime[user] will be read twice(one in condition and one in the inside of if statement) so we can cache it before the if statement to reduce one complex SLOAD by just risking losing 3 gas if the condition is false.

if (time2fullRedemption[user] > block.timestamp condition be true (time2fullRedemption[user] will be read twice from storage so we can cache it before the if statement to reduce one complex SLOAD with just risking losing 3 gas if the condition be false

if _totalShares == 0 is false _totalShares will be read twice and we can save 97 gas if we cache it before the if statement with just risking 3 gas loss if the condition turns out true

if vaultSafeCollateralRatio[pool] == 0 is false vaultSafeCollateralRatio[pool] will be read twice and we can reduce one complex SLOAD if we cache it before the if statement with just risking 3 gas loss if the condition turns out true

if vaultBadCollateralRatio[pool] == 0 is false vaultBadCollateralRatio[pool] will be read twice and we can reduce one complex SLOAD if we cache it before the if statement with just risking 3 gas loss if the condition turns out true

4. can make the variable outside the loop to save gas

make the variable outside the loop and only give the value to variable inside

pool

borrowed

5. Avoid a SLOAD optimistically

There is a chance that the first part will be true so the second part doesn’t need to be checked, so it is better to use the part that we have first instead of the part that needs to be called.

swap the 2 sides for the chance of price <= 1005000 being true and not using a SLOAD on premiumTradingEnabled

6. using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.

7. Ternary over if ... else

Using ternary operator instead of the if else statement saves gas.

8. Use assembly to check for address(0)

saves 6 gas per instance

9. state variable should be declared as constant which saves lots of gas

maxSupply is not being updated in this contract and it always has a constant value so it should be declared as a constant saving Gsset (20000 gas) and 100 gas per call

10. part of the code can be pre calculated

these parts of the code can be pre calculated and given to the contract as constants this will stop the use of extra operations even if its for code readability consider putting comments instead.

instead of 100 * 1e18 put the answer to it in the code to reduce one operation use

(86400 * 365) / 10000 can be pre calculated to reduce two operation uses

11. cache parts of code for second use

instead of doing some operations again answer can be cached at the first time and be used every where.

balance - preBalance is calculated in #L25 so if we cache it then we can use the cached version in #L31

same here

reward - eUSDShare

12. Non-usage of specific imports

The current form of relative path import is not recommended for use because it can unpredictably pollute the namespace. Instead, the Solidity docs recommend specifying imported symbols explicitly.

A good example:

import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {SafeCastLib} from "solmate/utils/SafeCastLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {IProducer} from "src/interfaces/IProducer.sol";
import {GlobalState, UserState} from "src/Common.sol";

#0 - c4-pre-sort

2023-07-27T21:29:17Z

JeffCX marked the issue as high quality report

#1 - c4-judge

2023-07-27T23:42:11Z

0xean marked the issue as grade-a

#2 - c4-sponsor

2023-07-29T09:01:00Z

LybraFinance marked the issue as sponsor acknowledged

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