Platform: Code4rena
Start Date: 28/09/2023
Pot Size: $36,500 USDC
Total HM: 5
Participants: 115
Period: 6 days
Judge: 0xDjango
Total Solo HM: 1
Id: 290
League: ETH
Rank: 24/115
Findings: 2
Award: $202.85
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xDetermination
Also found by: DeFiHackLabs, Norah, Pessimistic, PwnStars, SpicyMeatball, Testerbot, ThreeSigma, bin2chen, blutorque, deadrxsezzz, dirk_y, ether_sky, hals, neumo, rokinot, said, seerether, turvy_fuzz
198.4834 USDC - $198.48
https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/Prime.sol#L704-L719 https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/Prime.sol#L221 https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/Prime.sol#L827-L833
Whenever a critical parameter such as alphaNumerator, alphaDenominator, supplyMultiplier or borrowMultiplier is updated by the governance or the new market is added to prime reward programme, There is a need to update the scores of all the users for all the market to ensure fair distribution of the reward.
Current Implementation achieves this by doing calling updateScores(address[] users)
for batch of users multiple times, this is done to avoid running out gas. To track how many users have been updated so far protocol tracks two key variables pendingScoreUpdates
and totalScoreUpdatesRequired
.
Variable totalScoreUpdatesRequired
is stored as the total number of token in circulation (one token per user) at the beginning of the new round of update. Also pendingScoreUpdates
is also initially set to total of tokens (reflecting total users) and decremented subsequently on each score update for user.
function _startScoreUpdateRound() internal { nextScoreUpdateRoundId++; totalScoreUpdatesRequired = totalIrrevocable + totalRevocable; pendingScoreUpdates = totalScoreUpdatesRequired; }
Problem here is that while the totalScoreUpdatesRequired
and pendingScoreUpdates
are updated whenever the tokens are burned.
In similar manner when new users are minted prime tokens isScoreUpdated[nextScoreUpdateRoundId][user]
should be set True
as they score assign to them will be with latest parameter anyway and to restrict malicious user to re-update their score and unnecessarily reduce the pendingScoreUpdates
parameter.
Lets consider following scenario:
totalScoreUpdatesRequired
and pendingScoreUpdates
will be stored as :
totalScoreUpdatesRequired
= 3pendingScoreUpdates
= 3updateScores()
function, three new users are minted prime token.updateScores()
isScoreUpdated[nextScoreUpdateRoundId][user]
will hold false
value, making pendingScoreUpdates
to be reduced to zero in the processNow there is function accrueInterestAndUpdateScore(address user, address market)
, by calling repeatedly for each users and all of their markets one by one protocol or users can update the scores in time even after above exploit, but then this defeats the purpose of this whole mechanism, which is implemented to ensure no matter all users score are updated before any user can claim there reward as per newly updated parameters.
Also, protocol will most likely collect the list of users whose score to be updated by checking isScoreUpdated[nextScoreUpdateRoundId][user]
. In that case totally list will also include new users whose score is already updated, and as result parameter pendingScoreUpdates
will fall short to compelete all the updates via updateScores()
functionality.
tldr; isScoreUpdated[nextScoreUpdateRoundId][user]
and pendingScoreUpdates
are not in sync whenever new users are minted in between _startScoreUpdateRound()
and updateScores()
function calls, which can lead to unfair reward distribution.
Unfair distribution of the rewards.
Manual Review
Update the parameter isScoreUpdated[nextScoreUpdateRoundId][user]
to True
each time user is minted new token.
Other
#0 - c4-pre-sort
2023-10-06T23:50:40Z
0xRobocop marked the issue as duplicate of #555
#1 - c4-judge
2023-11-02T01:45:34Z
fatherGoose1 marked the issue as satisfactory
#2 - c4-judge
2023-11-05T00:52:22Z
fatherGoose1 changed the severity to 3 (High Risk)
🌟 Selected for report: Bauchibred
Also found by: 0x3b, 0xDetermination, 0xMosh, 0xScourgedev, 0xTheC0der, 0xTiwa, 0xWaitress, 0xdice91, 0xfusion, 0xpiken, 0xprinc, 0xweb3boy, ArmedGoose, Aymen0909, Breeje, Brenzee, Daniel526, DavidGiladi, DeFiHackLabs, Flora, Fulum, HChang26, Hama, IceBear, J4X, Krace, KrisApostolov, Maroutis, Mirror, MohammedRizwan, Norah, PwnStars, SPYBOY, TangYuanShen, Testerbot, ThreeSigma, Tricko, al88nsk, alexweb3, ast3ros, berlin-101, bin2chen, blutorque, btk, d3e4, deth, e0d1n, ether_sky, ge6a, gkrastenov, glcanvas, hals, imare, inzinko, jkoppel, jnforja, joaovwfreire, josephdara, kutugu, lotux, lsaudit, mahdirostami, merlin, n1punp, nadin, neumo, nisedo, nobody2018, oakcobalt, orion, peanuts, pep7siup, pina, ptsanev, rokinot, rvierdiiev, said, santipu_, sashik_eth, seerether, squeaky_cactus, terrancrypt, tonisives, twicek, vagrant, xAriextz, y4y
4.3669 USDC - $4.37
Prime.sol does not have any functionality to remove the market, once added in the allMarkets
Array.
There are at-least two scenarios where-in this functionality will be needed :
addMarket()
function, that was not supposed to be added.Also, as there is _ensureMaxLoop()
implemented, there is possibility that governance may not be able add any new markets after certain occurrence of the above two cases.
User may end up paying excess gas and in extreme scenario governance wont be able to add new Market.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
Manual Review
Implement a functionality to remove the markets from Allmarkets Array.
Other
#0 - c4-pre-sort
2023-10-06T23:49:19Z
0xRobocop marked the issue as duplicate of #421
#1 - c4-judge
2023-10-31T19:00:54Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#2 - c4-judge
2023-11-03T02:09:07Z
fatherGoose1 marked the issue as grade-b