rsrRewardsAtLastPayout is incorrectly updated to a smaller value in seizeRSR. This results in the staker pool receiving less rewards in subsequent reward payouts than expected.
Proof of Concept
In function seizeRSR, the amount of seized stakeRSR, draftRSR, and rewards are calculated based on the seized ratio, where seizedRatio = ceil(rsrAmount / rsrBalance). At line 472, the seized rsrRewards is calculated as (rewards * rsrAmount + (rsrBalance - 1)) / rsrBalance, and is then added to the total seizedRSR. Then the seized rsrRewards should be subtracted from the current rsrRewards to update rsrRewardsAtLastPayout. However, it is the total seizedRSR is subtracted from the current rsrRewards instead of the seized rsrRewards. This results in rsrRewardsAtLastPayout being incorrectly updated to a smaller value.
The rsrRewardsAtLastPayout is used in _payoutRewards to calculate the payout amount (L609-L610). If rsrRewardsAtLastPayout is incorrectly updated to a smaller value, the staker pool will receive less rewards than expected.
// Function: StRSR.sol#_payoutRewards()
600: // Do an actual payout if and only if enough RSR is staked!
601: if (totalStakes >= FIX_ONE) {
602: // Paying out the ratio r, N times, equals paying out the ratio (1 - (1-r)^N) 1 time.
603: // Apply payout to RSR backing
604: // payoutRatio: D18 = FIX_ONE: D18 - FixLib.powu(): D18
605: // Both uses of uint192(-) are fine, as it's equivalent to FixLib.sub().
606: uint192 payoutRatio = FIX_ONE - FixLib.powu(FIX_ONE - rewardRatio, numPeriods);
607:
608: // payout: {qRSR} = D18{1} * {qRSR} / D18
609:@> payout = (payoutRatio * rsrRewardsAtLastPayout) / FIX_ONE;
610:@> stakeRSR += payout;
611: }
Lines of code
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L471-L473
Vulnerability details
Impact
rsrRewardsAtLastPayout
is incorrectly updated to a smaller value inseizeRSR
. This results in the staker pool receiving less rewards in subsequent reward payouts than expected.Proof of Concept
In function
seizeRSR
, the amount of seizedstakeRSR
,draftRSR
, andrewards
are calculated based on the seized ratio, whereseizedRatio = ceil(rsrAmount / rsrBalance)
. At line 472, the seizedrsrRewards
is calculated as(rewards * rsrAmount + (rsrBalance - 1)) / rsrBalance
, and is then added to the totalseizedRSR
. Then the seizedrsrRewards
should be subtracted from the currentrsrRewards
to updatersrRewardsAtLastPayout
. However, it is the totalseizedRSR
is subtracted from the currentrsrRewards
instead of the seizedrsrRewards
. This results inrsrRewardsAtLastPayout
being incorrectly updated to a smaller value.https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L471-L473
The
rsrRewardsAtLastPayout
is used in_payoutRewards
to calculate the payout amount (L609-L610). IfrsrRewardsAtLastPayout
is incorrectly updated to a smaller value, the staker pool will receive less rewards than expected.https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L600-L611
Tools Used
VS Code
Recommended Mitigation Steps
Update the
rsrRewardsAtLastPayout
by subtracting the seizedrsrRewards
from the currentrsrRewards
instead of the totalseizedRSR
.Assessed type
Other