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: 81/132
Findings: 2
Award: $76.32
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: alexweb3
Also found by: D_Auditor, DedOhWale, DelerRH, LuchoLeonel1, Musaka, Neon2835, Silvermist, Timenov, TorpedoPistolIXC41, adeolu, cartlex_, hals, josephdara, koo, lanrebayode77, mahyar, mladenov, mrudenko, pep7siup, zaevlad, zaggle
18.4208 USDC - $18.42
https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/configuration/LybraConfigurator.sol#L85-L88 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/configuration/LybraConfigurator.sol#L90-L93 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/governance/GovernanceTimelock.sol#L25-L27 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/governance/GovernanceTimelock.sol#L29-L31
In LybraConfigurator
contract : onlyRole
& checkRole
implementations are flawed since there's no check for the returned boolean of GovernanceTimelock.checkOnlyRole(role, msg.sender)
& GovernanceTimelock.checkRole(role, msg.sender)
:
checkRole
function in the GovernanceTimelock
contract:
function checkRole(bytes32 role, address _sender) public view returns(bool){ return hasRole(role, _sender) || hasRole(DAO, _sender); }
checkOnlyRole
function in GovernanceTimelock
contract:function checkOnlyRole(bytes32 role, address _sender) public view returns(bool){ return hasRole(role, _sender); }
Anyone can access all setter functions in the LybraConfigurator
contract that are supposed to be accessable only by specific roles (TIMELOCK,ADMIN,DAO).
onlyRole
modifier in the LybraConfigurator
contract:File:2023-06-lybra/contracts/lybra/configuration/LybraConfigurator.sol Line 85-88: modifier onlyRole(bytes32 role) { GovernanceTimelock.checkOnlyRole(role, msg.sender); _; }
checkRole
modifier in the LybraConfigurator
contract:File:2023-06-lybra/contracts/lybra/configuration/LybraConfigurator.sol Line 90-93: modifier checkRole(bytes32 role) { GovernanceTimelock.checkRole(role, msg.sender); _; }
Manual Testing.
Wrap the returned boolean in both modifiers by require
so that it will revert if the caller isn't assigned a specific role to access the function:
modifier onlyRole(bytes32 role) { require(GovernanceTimelock.checkOnlyRole(role, msg.sender)); _; }
modifier checkRole(bytes32 role) { require(GovernanceTimelock.checkRole(role, msg.sender)); _; }
Access Control
#0 - c4-pre-sort
2023-07-09T02:05:57Z
JeffCX marked the issue as duplicate of #704
#1 - c4-judge
2023-07-28T15:24:06Z
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
57.9031 USDC - $57.90
ID | Title | Severity |
---|---|---|
L-01 | isOtherEarningsClaimable(address user) : division by zero is not prevented | Low |
L-02 | Different pools might use different esLBRBoost contract | Low |
L-03 | ProtocolRewardsPool contract: any user can free mint 3 esLBR tokens | Low |
L-04 | LBR & esLBR tokens addresses might be changed in different contracts | Low |
NC-01 | Boolean is compared to a boolean | Non Critical |
NC-02 | _checkHealth function visibility must be public | Non Critical |
NC-03 | Wrong error message | Non Critical |
isOtherEarningsClaimable(address user)
: division by zero is not prevented <a id="l-01" ></a>In EUSDMiningIncentives.sol
/isOtherEarningsClaimable(address user)
function : the result of stakedOf(user) could be zero if he doesn't have any borrowed amount in any pool.
This will cause run-time error when invoking isOtherEarningsClaimable(address user)
or getReward()
functions with an address with zero borrowed amounts.
File: 2023-06-lybra/contracts/lybra/miner/EUSDMiningIncentives.sol Line 188-190: function isOtherEarningsClaimable(address user) public view returns (bool) { return (stakedLBRLpValue(user) * 10000) / stakedOf(user) < 500; }
File: 2023-06-lybra/contracts/lybra/miner/EUSDMiningIncentives.sol Line 193: require(!isOtherEarningsClaimable(msg.sender), "Insufficient DLP, unable to claim rewards");
File: 2023-06-lybra/contracts/lybra/miner/EUSDMiningIncentives.sol Line 136-147: function stakedOf(address user) public view returns (uint256) { uint256 amount; for (uint i = 0; i < pools.length; i++) { ILybra pool = ILybra(pools[i]); uint borrowed = pool.getBorrowedOf(user); if (pool.getVaultType() == 1) { borrowed = borrowed * (1e20 + peUSDExtraRatio) / 1e20; } amount += borrowed; } return amount; //this amount will be zero if the user has no debts in any pool }
Manual Testing.
isOtherEarningsClaimable(address user)
function:
check if stakedOf(user)
is zero before doing the division:function isOtherEarningsClaimable(address user) public view returns (bool) { uint256 userDebts= stakedOf(user); return userDebts==0 ? false : (stakedLBRLpValue(user) * 10000) / userDebts < 500; }
esLBRBoost
contract <a id="l-02" ></a>-In EUSDMiningIncentives.sol
, ProtocolRewardsPool.sol
& stakerewardV2pool.sol
contracts:
each of the aforementioned contracts has a setBoost
function that changes the address of esLBRBoost
; while esLBRBoost
is the same contract that is going to be used across all pools ; then changing the address in one might un-sync with the others.
Changing the address of esLBRBoost
in one contract might un-sync with the others and causing problems.
Instances: 3
EUSDMiningIncentives.sol
:File: 2023-06-lybra/contracts/lybra/miner/EUSDMiningIncentives.sol Line 115-117: function setBoost(address _boost) external onlyOwner { esLBRBoost = IesLBRBoost(_boost); }
ProtocolRewardsPool.sol
:File: 2023-06-lybra/contracts/lybra/miner/ProtocolRewardsPool.sol Line 52-56: function setTokenAddress(address _eslbr, address _lbr, address _boost) external onlyOwner { esLBR = IesLBR(_eslbr); LBR = IesLBR(_lbr); esLBRBoost = IesLBRBoost(_boost); }
stakerewardV2pool.sol
:File: 2023-06-lybra/contracts/lybra/miner/stakerewardV2pool.sol Line 127-129: function setBoost(address _boost) external onlyOwner { esLBRBoost = IesLBRBoost(_boost); }
Manual Testing.
Allow setting the esLBRBoost
address in the LybraConfigurator
contract only; so other pools can only interact with this global state variable without each pool being able to set it.
ProtocolRewardsPool
contract: any user can free mint 3 esLBR tokens <a id="l-03" ></a>grabEsLBR(uint256 amount)
function: users can free mint 3 esLBR tokens if grabEsLBR(3) since there's no check on the amount of LBR tokens being burnt from the user .LBR.burn(msg.sender, (amount * grabFeeRatio) / 10000);
:
grabFeeRatio
is set to 3000
(can be updated by the owner to a value <= 8000).LBR.burn(msg.sender,3*3000/10000)
which is burning zero LBR tokens.grabableAmount
will be updated to reflect burning this dust amount of tokens;while these tokens are actually not burnt and still in circulation.Instances: 1
File: 2023-06-lybra/contracts/lybra/miner/ProtocolRewardsPool.sol Line 134: LBR.burn(msg.sender, (amount * grabFeeRatio) / 10000);
Manual Testing.
In grabEsLBR(uint256 amount)
function : Check that the burned LBR tokens > 0.
EUSDMiningIncentives.sol
contract by the owner.ProtocolRewardsPool.sol
contract by the owner.LybraConfigurator
)The tokens addresses might un-sync with other contracts which will cause chaos of calculations where the returned values of token.balanceOf(address) might be incorrect since the user has been minted esLBR from an address and the balance is retreived from other contract address.
Instances: 2
EUSDMiningIncentives.sol
:File:2023-06-lybra/contracts/lybra/miner/EUSDMiningIncentives.sol Line 84-87: function setToken(address _lbr, address _eslbr) external onlyOwner { LBR = _lbr; esLBR = _eslbr; }
ProtocolRewardsPool.sol
:File:2023-06-lybra/contracts/lybra/miner/ProtocolRewardsPool.sol Line 52-56: function setTokenAddress(address _eslbr, address _lbr, address _boost) external onlyOwner { esLBR = IesLBR(_eslbr); LBR = IesLBR(_lbr); esLBRBoost = IesLBRBoost(_boost); }
Manual Testing.
Allow setting the LBR & esLBR tokens addresses in the LybraConfigurator
contract only; so other contracts can only interact with these global state variables without any contract being able to set their addresses.
Instances: 1
File:2023-06-lybra/contracts/lybra/governance/LybraGovernance.sol Line 82: require(receipt.hasVoted == false, "GovernorBravo::castVoteInternal: voter already voted");
Manual Testing.
require(!receipt.hasVoted, "GovernorBravo::castVoteInternal: voter already voted")
_checkHealth
function visibility must be public<a id="nc-02" ></a>_checkHealth
function visibility must be public to enable liquidators from tracking & checking positions and liquidate the unhealthy spotted positions.
Instances: 2
File:2023-06-lybra/contracts/lybra/pools/base/LybraEUSDVaultBase.sol Line 291: function _checkHealth(address _user, uint256 _assetPrice) internal view {
File:2023-06-lybra/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol Line 225: function _checkHealth(address user, uint256 price) internal view {
Manual Testing.
Change _checkHealth
function visibility to public.
In LybraPeUSDVaultBase.sol
: wrong error message when checking for peUSD allowance of the providerto the pool:
"provider should authorize to provide liquidation EUSD"
while it should be:
"provider should authorize to provide liquidation PEUSD"
Instances: 1
File:2023-06-lybra/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol Line 131: require(PeUSD.allowance(provider, address(this)) > 0, "provider should authorize to provide liquidation EUSD");
Manual Testing.
Modify error message to : "provider should authorize to provide liquidation PEUSD".
#0 - JeffCX
2023-07-27T20:59:54Z
ProtocolRewardsPool contract: any user can free mint 3 esLBR tokens
duplicate of #156
#1 - c4-pre-sort
2023-07-27T21:00:00Z
JeffCX marked the issue as high quality report
#2 - JeffCX
2023-07-27T21:00:52Z
_checkHealth function visibility must be public
this one is a valuable QA
#3 - c4-judge
2023-07-28T00:05:44Z
0xean marked the issue as grade-a
#4 - c4-sponsor
2023-07-29T10:17:31Z
LybraFinance marked the issue as sponsor acknowledged