Rewards may be spread out among the **wrong time period** due to the way the protocol calculates it #133
Labels
2 (Med Risk)
Assets not at direct risk, but function/availability of the protocol could be impacted or leak value
bug
Something isn't working
downgraded by judge
Judge downgraded the risk level of this issue
M-27
primary issue
Highest quality submission among a set of duplicates
🤖_primary
AI based primary recommendation
satisfactory
satisfies C4 submission criteria; eligible for awards
selected for report
This submission will be included/highlighted in the audit report
sponsor confirmed
Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")
sufficient quality report
This report is of sufficient quality
Lines of code
https://github.com/code-423n4/2024-07-loopfi/blob/57871f64bdea450c1f04c9a53dc1a78223719164/src/reward/MultiFeeDistribution.sol#L1202-L1204
Vulnerability details
Bug Description
First, lets reference how the rewards are calculated when a 2nd incentive is introduced while another incentive is still within its
rewardsDuration
period.In
MultiFeeDistribution.sol
's_notifyReward
function:This will cause rewards from the initial incentive to be delayed and spread out across the 2nd incentive's period and initial stakers will have wrong amount of claimable rewards during the timestamps all the way until the 2nd incentive's
rewardsDuration
ends. Other than delays, the original staker could also possibly permanently lose some of their deserved reward tokens(described further below in "2nd way of exploit" section)Proof of Code
The symbol diagram below demostrates the senario ran in the foundry test
rewardsDuration = 30 days
.day 0
, whereA
represents the first half of the rewards that should be given out during the first half of the first incentive'srewardDuration
period. AndB
represents the second half respectively.day 15
, whereC
represents all the rewards from the 2nd incentive to be rewarded.Console Output:
Explanation:
10000 ether
) at day 0.A
as seen in the symbol diagram. (4999999999999999999999~=5000 ether
)10000 ether
) is introduced at day 15, causing remaining rewards leftover from the first incentive to be incorrectly stretched until the end of the 2nd incentive'srewardDuration
12 500 ether
.Let's examine Alice's balance at the end of 30 day:
12 500 ether
=5000 ether
+2500 ether
+5000 ether
=A
+B/2
+C/2
.However, the rightful amount of rewards her balance should be at day 30 is =
A
+B
+C/2
=15 000 ether
.Hence the remaining
15 000 ether
-12 500 ether = 2 500 ether
that Alice is entitled to claim at day 30, will only be given throughout day 30 to 45.This is very unfair to Alice who has staked her tokens since the beginning of the first incentive, and now she has to wait longer for the rewards from the first incentive which is a high oppportunity cost incurred for her.
This is made worse if the incentive given at the 2nd wave is significantly smaller than the original amount in wave 1, because it means she will have to wait longer for her significant rewards from the 1st wave, all because of the 2nd wave of small and insignificant incentive.
2nd way of exploit
Referencing the same senario in the Proof of Code section. A malicious staker can choose to stake anytime between day 30 to day 45 and cause Alice to permanently lose some of her rewards.
Example:
stake
on day 30.B
, even though he is not supposed to, because we already established in the symbol diagram that partB
is supposed to end on day 30. And since the malicious staker only staked on day 30, he should not be getting the rewards as he locked his tokens late.Hence, Alice permanently loses a portion of reward part
B
to the malicious staker who is not supposed to receive it.Overall Disclaimer: The example above of the 2nd incentive being introduced at exactly halfway(15 days) of the 1st incentive's duration was just used as an example, this bug still exists as long as the 2nd incentive is introduced at any point of time throughout the 1st incentive's duration, causing the respective portion to be spread across the wrong period.
Recommended Mitigation Steps
We can use a queue-like list to store rewards and their respective
periodFinish
, as well as a counter that we can increment whenrewards[rewardCounter].periodFinish
<block.timestamp
.And
rewards[i].rewardsPerSecond
is meant to be distributed between the timeframe ofrewards[i-1].periodFinish
torewards[i].periodFinish
only.Tools Used
Manual Review, Foundry, VSCode
Assessed type
Math
The text was updated successfully, but these errors were encountered: