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: 26/132
Findings: 3
Award: $432.66
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: No12Samurai
145.801 USDC - $145.80
In ProtocolRewardsPool.getReward function, peUSDBalance
gets compared to eUSD shares, which may cause unexpected actions for user, who is trying to withdraw his reward, and for protocol, who unexpectedly would send different tokens.
In getReward:L201 function checks, if peUSDBalance >= reward - eUSDShare
.
if(peUSDBalance >= reward - eUSDShare) { peUSD.transfer(msg.sender, reward - eUSDShare); emit ClaimReward(msg.sender, EUSD.getMintedEUSDByShares(eUSDShare), address(peUSD), reward - eUSDShare, block.timestamp); } else { if(peUSDBalance > 0) { peUSD.transfer(msg.sender, peUSDBalance); } ERC20 token = ERC20(configurator.stableToken()); uint256 tokenAmount = (reward - eUSDShare - peUSDBalance) * token.decimals() / 1e18; token.transfer(msg.sender, tokenAmount); emit ClaimReward(msg.sender, EUSD.getMintedEUSDByShares(eUSDShare), address(token), reward - eUSDShare, block.timestamp); }
The issue is that peUSDBalance
is not the same as eUSD shares.
Proof - in converToPeUSD function in PeUSDMainnetStableVision.sol
contract eusdAmount
, not eUSD share, gets converted to peUSD token.
This means that getReward
contract may unexpectedly send configurator.stableToken
instead of peUSD token, which may be a loss for a protocol.
Manual review
Make sure that peUSDBalance
gets converted to shared before it is compared to reward
and eUSDShare
Context
#0 - c4-pre-sort
2023-07-11T15:42:07Z
JeffCX marked the issue as primary issue
#1 - c4-sponsor
2023-07-18T09:02:35Z
LybraFinance marked the issue as sponsor confirmed
#2 - c4-judge
2023-07-26T13:28:48Z
0xean marked the issue as duplicate of #869
#3 - c4-judge
2023-07-28T15:36:58Z
0xean marked the issue as satisfactory
#4 - c4-judge
2023-07-29T22:06:38Z
0xean marked the issue as duplicate of #604
🌟 Selected for report: SpicyMeatball
202.5014 USDC - $202.50
https://github.com/code-423n4/2023-06-lybra/blob/824de2eefa30101622b65e43b79886667abad916/contracts/lybra/miner/EUSDMiningIncentives.sol#L153-L154 https://github.com/code-423n4/2023-06-lybra/blob/824de2eefa30101622b65e43b79886667abad916/contracts/lybra/miner/EUSDMiningIncentives.sol#L188-L190
Malicious users can manipulate the etherInLp
and lbrInLp
values which are used to calculate staked LBR LP value. As a result - malicious users in that way can purchase other people's earnings by manipulating the token amount in WETH/LBR Uniswap V2 pool with flash swaps, when it should not be possible.
The function stakedLBRLpValue
calculates the user's value of staked tokens in ethlbrStakePool
function stakedLBRLpValue(address user) public view returns (uint256) { uint256 totalLp = IEUSD(ethlbrLpToken).totalSupply(); (, int etherPrice, , , ) = etherPriceFeed.latestRoundData(); (, int lbrPrice, , , ) = lbrPriceFeed.latestRoundData(); uint256 etherInLp = (IEUSD(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2).balanceOf(ethlbrLpToken) * uint(etherPrice)) / 1e8; uint256 lbrInLp = (IEUSD(LBR).balanceOf(ethlbrLpToken) * uint(lbrPrice)) / 1e8; uint256 userStaked = IEUSD(ethlbrStakePool).balanceOf(user); return (userStaked * (lbrInLp + etherInLp)) / totalLp; }
This function's returned value is used in the isOtherEarningsClaimable
function, which determines if the user's earnings can be claimable by others.
function isOtherEarningsClaimable(address user) public view returns (bool) { return (stakedLBRLpValue(user) * 10000) / stakedOf(user) < 500; }
The issue is that the balance of WETH and LBR tokens in WETH/LBR Uniswap V2 can be easily manipulated by calling flash swap on the pool, which withdraws the tokens from the pool.
Bob has borrowed eUSD from one of the Vaults and staked in ethlbrStakePool
After some time - Alice the attacker sees that if she does a flash swap on WETH/LBR pool (removes WETH and LBR tokens from the pool), she can claim Bob's earnings by calling purchaseOtherEarnings
. That is because flash swap manipulates stakedLBRLpValue
value.
As a result - When Bob thought he was not in danger to withdraw his rewards, Alice took advantage of the Uniswap V2 flash swap and took Bob's rewards.
Manual Review
It is not recommended to rely on Uniswap V2 pool balances, because they can be easily manipulated by calling flash swaps on them.
Uniswap
#0 - c4-pre-sort
2023-07-10T20:35:38Z
JeffCX marked the issue as duplicate of #442
#1 - c4-judge
2023-07-25T23:53:20Z
0xean changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-07-28T15:42:01Z
0xean marked the issue as satisfactory
84.3563 USDC - $84.36
In ProtocolRewardsPool.getReward function, if msg.sender
rewards is 2x or more than the balance of eUSD tokens in ProtocolRewardsPool
, the user will not be able to get any rewards
eUSDShare
value in getReward
function depends on whether the contract balance is more than the user rewards amount.
uint256 eUSDShare = balance >= reward ? reward : reward - balance;
After that eUSDShare
value is used in EUSD.transferShare
function to send the rewards to user
EUSD.transferShares(msg.sender, eUSDShare);
The issue is that if the reward is twice or more than the balance, getReward
function will always fail.
User rewards - 1000 Contract balance - 300
This means that eUSDShare
amount will be 1000 - 300 = 700
Since the contract only has 300, EUSD.transferShares(msg.sender, eUSDShare)
function will fail, because the contract doesn't have enough funds even though the expected action would be to send peUSD and configurator.stableToken
if there is not enough eUSD in the contract
Manual Review
Change the eUSDShare
line to the example below. This guarantees that the user can receive his reward even if there is not enough eUSD token.
uint256 eUSDShare = balance >= reward ? reward : balance;
Token-Transfer
#0 - c4-pre-sort
2023-07-10T02:15:10Z
JeffCX marked the issue as duplicate of #869
#1 - c4-pre-sort
2023-07-10T13:49:14Z
JeffCX marked the issue as not a duplicate
#2 - c4-pre-sort
2023-07-10T13:49:39Z
JeffCX marked the issue as duplicate of #161
#3 - c4-judge
2023-07-28T15:44:23Z
0xean marked the issue as satisfactory