Open sherlock-admin3 opened 4 months ago
High severity because this will occur without an attacker and between all Bribe Rewards, resulting in reward loss for all voters. A side effect of this is that rewards sent to Bribe Rewards will be stuck in the contract forever.
The protocol team fixed this issue in the following PRs/commits: https://github.com/metropolis-exchange/magicsea-staking/pull/20
Escalate
The first thing to remember is that this issue is different from undistributed/ unaccrued rewards getting stuck in the contract due to many reasons like : no voting, precision loss etc. That is a different set of issue reported elsewhere and is meant for “undistributed rewards”. Whereas the current issue set #52 describes a bug that the rewards that users had “already accrued” can not be claimed by them, so this is a problem of claiming accrued rewards rather than undistributed rewards. Fixing either of them will not fix the other, so these are two different sets of issues.
Now lets look at some issues wrongly duped with this #52
Escalate
The first thing to remember is that this issue is different from undistributed/ unaccrued rewards getting stuck in the contract due to many reasons like : no voting, precision loss etc. That is a different set of issue reported elsewhere and is meant for “undistributed rewards”. Whereas the current issue set #52 describes a bug that the rewards that users had “already accrued” can not be claimed by them, so this is a problem of claiming accrued rewards rather than undistributed rewards. Fixing either of them will not fix the other, so these are two different sets of issues.
Now lets look at some issues wrongly duped with this #52
303 is not a duplicate and is invalid. When the first call is made totalRewards returned = 0 because totalSupply = 0 and in this first call while setting the reward parameters rewarder.lastUpdateTimestamp gets updated to the startTimestamp of the reward cycle. So the assertion made in this submission “rewards calculated for time when lastUpdateTimestamp was zero will be huge” is wrong. For all later calls, this actual non-zero lastUpdateTimestamp will be used, so the calculation fo rewards is correct.
397 is not a duplicate and is invalid because even though the totalRewards calculation is wrong it has no real impact as the “totalRewards” are manifested into storage using rewarder.update which returns 0 because the oldTotalSupply was zero in the mentioned case of first voter in a briberewarder’s bribe periods. Have a look at rewarder.update here. So oldTotalSupply and oldBalance being zero, this rewards calculation is completely ignored i current logic and no one can earn extra rewards as claimed in the issue. Hence invalid.
496 is a duplicate of #164 (note :also read the escalation on #164 as this issue #496 overall describes “undistributed rewards will be stuck” is a duplicate of set 2 of the issues mentioned in that escalation) because some of the issues in set 2 of #164 also describe precision loss as a way of having funds stuck in the contract. For example see #172 also mentioned in the escalation on #164.
582 is also not a duplicate and is invalid because the bug has no impact due to similar reason as #397 as explained above.
588 is invalid due to the same reasons as #397. Ping me if you want to discuss this bug more.
607 is again invalid. It goes over the same assertion that rewards calculation will yield a huge value because lastUpdateTimestamp is a time in past before the period started, but misses the point that this bug never materializes due to how the rewarder2 library works. If the totalsupply before the call was zero (which means this is the first call) these “totalRewards” never accrue in the accDebtPerShare and hence are not distributed. On the next call, these are accrued correctly based on the time interval from this first call to the second call. So the accrued rewards never includes the rewards calculated for time before the first bribe period started. Hence, similar to #397 and invalid.
356 is not a duplicate of this but a duplicate of #164 set 2. It talks about a portion of rewards that was undistributed getting stuck in the BribeRewarder contract. See comment for #496 above.
You've created a valid escalation!
To remove the escalation from consideration: Delete your comment.
You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final.
Issue #582 was related to an incorrect update inside the function _lastUpdateTimestamp _modify(). This was not actually a problem in the code as implemented.
Issue #52 highlights a different and real issue in the design of the reward calculation system. This issue relates to how the single _lastUpdateTimestamp
variable is used across different periods, which could lead to a loss of rewards for some users.
2 different sets ; I don't agree
@0xSmartContract
2 different sets ; I don't agree
I think there is some misunderstanding. I did not say that this issue has 2 sets. I only pointed out specific issues which are invalid and wrongly duped with this. Rest assured, this is only a single issue and not two sets, but please go over the invalid ones and remove them from the dups (list mentioned in original escalation).
@0xSmartContract
2 different sets ; I don't agree
I think there is some misunderstanding. I did not say that this issue has 2 sets. I only pointed out specific issues which are invalid and wrongly duped with this. Rest assured, this is only a single issue and not two sets, but please go over the invalid ones and remove them from the dups (list mentioned in original escalation).
I already said "I don't agree" for the set part, so my "I aggre" here can be taken into account.
https://github.com/sherlock-audit/2024-06-magicsea-judging/issues/52#issuecomment-2267165669
I agree with the escalation and plan to accept it. Here are the changes I'm going to apply:
Result: High Has duplicates
The Lead Senior Watson signed off on the fix.
jsmi
High
A voter lose bribe rewards if another voter voted before claim.
Summary
A voter lose bribe rewards if another voter voted before claim.
Vulnerability Detail
This problem is related to design architecture. In
BribeRewarder.sol
, the_lastUpdateTimestamp
is used to calculate the unclaimed rewards forperiodId
, but it is not dependent onperiodId
. Therefore, once_lastUpdateTimestamp
has been updated to the next period, there is no way to calculate the unclaimed rewards for the previous period.The following is the modified test code for PoC.
The claimed rewards amount of alice and bob for 1st period are originally
75e17
and25e17
, respectively. But if a voter votes for 2nd period before alice and bob claim their rewards for 1st period, the claimed rewards amount of alice and bob will be decreased to50e17
andzero
, respectively. It means that the rewards for [50,100] of 1st period are will not be claimed.And the following is the test command and result.
As shown above, if anyone votes before alice and bob claim their rewards, the rewards of alice and bob will be decreased.
Impact
Voters lose bribe rewards if another voter voted before claim. And such cases can occur frequently enough.
Code Snippet
Tool used
Manual Review
Recommendation
Change the
_lastUpdateTimestamp
state variable to be dependent onperiodId
. For instance, change it to mapping variable such asmapping(uint256 periodId => uint256) _lastUpdateTimestamp;
.