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
Rank: 29/132
Findings: 5
Award: $318.68
🌟 Selected for report: 0
🚀 Solo Findings: 0
80.4648 USDC - $80.46
According to the LybraGovernance.sol contract lines 120-122 forVotes is stored in proposalData[proposalId].supportVotes[0], while againstVotes in proposalData[proposalId].supportVotes[1]
forVotes = proposalData[proposalId].supportVotes[0]; againstVotes = proposalData[proposalId].supportVotes[1]; abstainVotes = proposalData[proposalId].supportVotes[2];
Vote succeeded when the number of "for" votes is bigger then "against" votes, but _voteSucceeded() function returns true when opposite (against > for)
DAO voted for proposal would not be possible to execute and thus DAO would be effectevely not-usable.
Change comparison in _voteSucceeded() function to
return proposalData[proposalId].supportVotes[0] > proposalData[proposalId].supportVotes[1]
Governance
#0 - c4-pre-sort
2023-07-04T02:22:23Z
JeffCX marked the issue as duplicate of #15
#1 - c4-judge
2023-07-28T15:33:05Z
0xean marked the issue as satisfactory
🌟 Selected for report: T1MOH
Also found by: Iurii3, LokiThe5th, josephdara, skyge, squeaky_cactus, yjrwkk, zambody
221.4353 USDC - $221.44
Governance quorum reached is when the sum of votes bigger then quorum threshold. Currently, _quorumReached function returns true if
proposalData[proposalId].supportVotes[1] + proposalData[proposalId].supportVotes[2] >= quorum(proposalSnapshot(proposalId));
However, according to the LybraGovernance.sol contract lines 120-122 - proposalData[proposalId].supportVotes[1] is againstVotes, proposalData[proposalId].supportVotes[2] is abstainVotes
forVotes = proposalData[proposalId].supportVotes[0]; againstVotes = proposalData[proposalId].supportVotes[1]; abstainVotes = proposalData[proposalId].supportVotes[2];
And function _quorumReached will return True only when amount of against and abstrain votes bigger then threshold
Votes will unlikely get enough against/abstain votes to pass threshold and proposals won't be exetucted
Use total amount of votes as a base for calculating _quorumReached
function _quorumReached(uint256 proposalId) internal view override returns (bool){ return proposalData[proposalId].totalVotes >= quorum(proposalSnapshot(proposalId)); }
Governance
#0 - c4-pre-sort
2023-07-09T13:10:24Z
JeffCX marked the issue as duplicate of #14
#1 - c4-judge
2023-07-28T15:33:46Z
0xean marked the issue as satisfactory
#2 - c4-judge
2023-07-28T19:42:05Z
0xean changed the severity to 3 (High Risk)
🌟 Selected for report: hl_
Also found by: 0xRobocop, Co0nan, CrypticShepherd, DedOhWale, Iurii3, Kenshin, Musaka, OMEN, RedOneN, SpicyMeatball, Toshii, Vagner, bytes032, cccz, gs8nrv, hl_, kenta, lanrebayode77, mahdikarimi, max10afternoon, peanuts, pep7siup
5.5262 USDC - $5.53
User borrowed amount and poolTotalPeUSDCirculation amount calculated incorrectly and will be lower then they should be.
User's debt has two parts:
Executing _repay() function firstly cover feeStored[user] and then everything that is left should be deducted from the borrowed[_onBehalfOf]. The amount deducted from the borrowed[_onBehalfOf] should be calculated as amount less coveredFee, instead it just subtract the whole amount
borrowed[_onBehalfOf] -= amount; poolTotalPeUSDCirculation -= amount;
Moreover, these changes should be made only if repay amount > totalFee as poolTotalPeUSDCirculation do not include feeStored
These lines should be moved into the true branch of if(amount >= totalFee) statement
borrowed[_onBehalfOf] -= amount - totalFee; poolTotalPeUSDCirculation -= amount - totalFee;
Math
#0 - c4-pre-sort
2023-07-09T12:22:23Z
JeffCX marked the issue as duplicate of #532
#1 - c4-judge
2023-07-28T15:39:31Z
0xean marked the issue as satisfactory
🌟 Selected for report: bytes032
Also found by: 0xMAKEOUTHILL, 0xgrbr, 0xkazim, 0xnacho, Arz, Co0nan, CrypticShepherd, Cryptor, HE1M, Iurii3, LaScaloneta, LokiThe5th, LuchoLeonel1, MrPotatoMagic, Musaka, Qeew, RedTiger, SovaSlava, Toshii, Vagner, a3yip6, azhar, bart1e, devival, hl_, jnrlouis, kutugu, peanuts, pep7siup, qpzm, smaul
1.3247 USDC - $1.32
https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraRETHVault.sol#L46-L48 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraWbETHVault.sol#L34-L36
wbETH and rETH contract provides information about their price comparing to the ETH. This information is used by the Lybra Finance in getAssetPrice() function to calculate the actual price of the wbETH and rETH contracts may return the while depositing in vault, minting, calculating liquidation, withdrawing from pool.
Currently, the getAssetPrice() implemented incorrectly and will always revert making it impossible to use nearly every functionality of the project.
Correct name of the function to get the price is getExchangeRate() for rETH and exchangeRate() for wbETH.
Change code in LybraRETHVault.solL46-48 to
function getAssetPrice() public override returns (uint256) { return (_etherPrice() * IRETH(address(collateralAsset)).getExchangeRate()) / 1e18; }
Change code in LybraWbETHVault.solL34-36 to
function getAssetPrice() public override returns (uint256) { return (_etherPrice() * IWBETH(address(collateralAsset)).exchangeRate()) / 1e18; }
Math
#0 - c4-pre-sort
2023-07-04T13:15:55Z
JeffCX marked the issue as duplicate of #27
#1 - c4-judge
2023-07-28T17:14:15Z
0xean changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-07-28T17:15:00Z
0xean marked the issue as satisfactory
🌟 Selected for report: 0xnev
Also found by: 0xRobocop, 0xbrett8571, 0xkazim, 0xnacho, 3agle, 8olidity, ABAIKUNANBAEV, Bauchibred, Co0nan, CrypticShepherd, D_Auditor, DelerRH, HE1M, Iurii3, Kaysoft, MrPotatoMagic, RedOneN, RedTiger, Rolezn, SanketKogekar, Sathish9098, Timenov, Toshii, Vagner, bart1e, bytes032, codetilda, devival, halden, hals, kutugu, m_Rassska, naman1778, nonseodion, seth_lawson, solsaver, squeaky_cactus, totomanov, y51r, yudan, zaevlad
9.931 USDC - $9.93
Should use IPeUSD instead of IEUSD. However, this mistake won't escalate into bigger error as IEUSD interface has all necessary functions
Correct line of code: if (address(peUSD) == address(0)) peUSD = IPeUSD(_peusd);
setTokenMiner function has two arguments - array of _contracts and array of _bools. In case their length is not equal function will revert while using extensive amount of gas in a for loop.
Adding require(_contracts.length == _bools.length, "Length of contracts not equal to bools");
will save gas in this situation
Considering Lybra Fincance allow users to extend the time of lock-up, this require statement is not correctly implemented.
For example
Team should consider rewriting requirement to
require(userStatus.unlockTime <= block.timestamp + _setting.duration, "Your lock-in period has not ended, and the term can only be extended, not reduced.");
LybraEUSDVaultBase.solL73 LybraPeUSDVaultBase.solL59
Currently Lybra Vaults may be used with 18-decimal LSD assets - stETH, wstETH, rETH, wbETH.
As it stated in Lybra docs "Collateral is any asset that a borrower must provide to take out a loan, acting as security for the debt."
Should there appear LSD asset with lower decimals, or if team decides to add 6-decimal USDC/USDT as a possible collateral this requirement require(assetAmount >= 1 ether, "")
would be passed only with much bigger amount then anticipated. For 6 decimal assets user would need to deposit 1_000_000_000_000 token to pass this requirement
Team might consider adding mapping with assets decimals (address => uint) or move this requirement from base abstract contract to higher level contract for particular asset
Check before transfer would save gas cost in case of revert LybraEUSDVaultBase.solL109
Wrong notation. Should use collateral asset instead of stETH LybraPeUSDVaultBase.solL80 LybraPeUSDVaultBase.solL123 LybraPeUSDVaultBase.solL149
Unnecessary assignment LybraRETHVault.solL18
rkPool value is added during the deployment in constructor, no need to add it directly in the contract.
uint256 maxSupply = 100_000_000 * 1e18;
in line 20 while 55 mln in notation
EUSD.solL149 EUSD.solL196 EUSD.solL222 EUSD.solL246 EUSD.solL266 EUSD.solL330 EUSD.solL364 EUSD.solL389
functions has "the contract must not be paused" in notation but actually do not implement any check for pause
EUSD.sol imports SafeMath library and uses it for most arithmetical operations. But in two occasions += and -= used. Consider changing to SafeMath .add/.sub for consistency
#0 - JeffCX
2023-07-27T19:42:51Z
L8 duplicate of #18
#1 - c4-judge
2023-07-28T00:03:58Z
0xean marked the issue as grade-b
#2 - c4-sponsor
2023-07-29T09:39:42Z
LybraFinance marked the issue as sponsor acknowledged
#3 - c4-sponsor
2023-07-29T09:39:46Z
LybraFinance marked the issue as sponsor confirmed