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: 76/115
Findings: 1
Award: $4.37
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
Low Risk & Non Critical Findings 5
Low-Risk Findings List
Number | Issue Detail | Severity |
---|---|---|
L-01 | totalScoreUpdatesRequired is not reduced after each iteration in updateScores() | Low |
L-02 | claimTimeRemaining() is not implemented correctly. | Low |
L-03 | Missing function to change the minimum XVS token required for Prime Token . | Low |
L-04 | getEffectiveDistributionSpeed() should check if the token has been initialised. | Low |
L-05 | _claimInterest() should check if the user has a Prime Token first. | Low |
Total ~ 5 Issues
totalScoreUpdatesRequired
is not reduced after each iteration in updateScores()
.totalScoreUpdatesRequired
is a variable used to track the total number of accounts whose score needs to be updated. When updating the scores for each user this variable needs to be reduced as well to ensure proper accounting, but in the function updateScores()
this is not done leading to improper tracking of the variable.
The variable in question;
/// @notice total number of accounts whose score needs to be updated uint256 public totalScoreUpdatesRequired;
In the function updateScores()
, totalScoreUpdatesRequired
is not reduced after a score has been updated.
function updateScores(address[] memory users) external { if (pendingScoreUpdates == 0) revert NoScoreUpdatesRequired(); if (nextScoreUpdateRoundId == 0) revert NoScoreUpdatesRequired(); for (uint256 i = 0; i < users.length; ) { address user = users[i]; if (!tokens[user].exists) revert UserHasNoPrimeToken(); if (isScoreUpdated[nextScoreUpdateRoundId][user]) continue; address[] storage _allMarkets = allMarkets; for (uint256 j = 0; j < _allMarkets.length; ) { address market = _allMarkets[j]; _executeBoost(user, market); _updateScore(user, market); unchecked { j++; } } pendingScoreUpdates--; isScoreUpdated[nextScoreUpdateRoundId][user] = true; unchecked { i++; } emit UserScoreUpdated(user); } }
The variable totalScoreUpdatesRequired
should be reduced after each interaction.
function updateScores(address[] memory users) external { if (pendingScoreUpdates == 0) revert NoScoreUpdatesRequired(); if (nextScoreUpdateRoundId == 0) revert NoScoreUpdatesRequired(); for (uint256 i = 0; i < users.length; ) { address user = users[i]; if (!tokens[user].exists) revert UserHasNoPrimeToken(); if (isScoreUpdated[nextScoreUpdateRoundId][user]) continue; address[] storage _allMarkets = allMarkets; for (uint256 j = 0; j < _allMarkets.length; ) { address market = _allMarkets[j]; _executeBoost(user, market); _updateScore(user, market); unchecked { j++; } } pendingScoreUpdates--; totalScoreUpdatesRequired--; isScoreUpdated[nextScoreUpdateRoundId][user] = true; unchecked { i++; } emit UserScoreUpdated(user); } }
claimTimeRemaining()
is not implemented correctlyThe function claimTimeRemaining()
is implemented correctly as it returns the wrong output for if (stakedAt[user] == 0).
When the (stakedAt[user] == 0) this signifies that the user is not eligible for Prime Token
that is their XVS balance is below 1000 tokens.
but the function treats (stakedAt[user] == 0) as if the staker is eligible by returning the STAKING_PERIOD
.
function claimTimeRemaining(address user) external view returns (uint256) { if (stakedAt[user] == 0) return STAKING_PERIOD; uint256 totalTimeStaked = block.timestamp - stakedAt[user]; if (totalTimeStaked < STAKING_PERIOD) { return STAKING_PERIOD - totalTimeStaked; } else { return 0; } }
The function should rearranged to return not eligible
if the (stakedAt[user] == 0).
Prime Token
.Cryptocurrencies are very volatile and as the protocol evolves great changes in the price of the XVS Token can occur which will affect Venus Prime if it is not built to adapt to these unexpected changes.
Venus prime lacks a function to change the MINIMUM_STAKED_XVS
. As of now XVS is worth roughly 4.5 dollars but with time its value may rise to 150 dollars or fall greatly to be worth a couple of cents, this leads to the 1000 XVS being too high for the average guy or too easy for everyone to get respectively. This will disrupt Venus Prime as it will no longer be able to achieve its aim of increasing user engagement with the protocol.
An access-controlled function to change the value of the MINIMUM_STAKED_XVS
should be added to prime.sol.
getEffectiveDistributionSpeed()
should check if the token has been initialised.The function does not check if the token has been initialised.
The function getEffectiveDistributionSpeed()
used to get rewards per block for a token does not check if the token has been initialised therefore, if an unknown token is inputted as a parameter will return unexpected values.
function getEffectiveDistributionSpeed(address token_) external view returns (uint256) { uint256 distributionSpeed = tokenDistributionSpeeds[token_]; uint256 balance = IERC20Upgradeable(token_).balanceOf(address(this)); uint256 accrued = tokenAmountAccrued[token_]; if (balance - accrued > 0) { return distributionSpeed; } return 0; }
The check should be added to the function.
function getEffectiveDistributionSpeed(address token_) external view returns (uint256) { _ensureTokenInitialized(token_); uint256 distributionSpeed = tokenDistributionSpeeds[token_]; uint256 balance = IERC20Upgradeable(token_).balanceOf(address(this)); uint256 accrued = tokenAmountAccrued[token_]; if (balance - accrued > 0) { return distributionSpeed; } return 0; }
_claimInterest()
should check if the user has a Prime Token
first.The internal function _claimInterest()
should check if the user has a Prime Token
before executing another logic.
In the external claimInterest()
and the internal _claimInterest()
functions the user is not checked to ensure that the address has a Prime Token
holder, this leaves the function open to be called by anyone and furthermore leaves a backdoor for which although not seen now can be exploited by malicious actors to benefit themselves or harm the protocol in the future.
Add a check to ensure that the user is a Prime Token
holder before logic for claiming rewards is executed.
function _claimInterest(address vToken, address user) internal returns (uint256) { if (!tokens[user].exists) revert UserHasNoPrimeToken(); uint256 amount = getInterestAccrued(vToken, user); amount += interests[vToken][user].accrued; interests[vToken][user].rewardIndex = markets[vToken].rewardIndex; interests[vToken][user].accrued = 0; address underlying = _getUnderlying(vToken); IERC20Upgradeable asset = IERC20Upgradeable(underlying); if (amount > asset.balanceOf(address(this))) { address[] memory assets = new address[](1); assets[0] = address(asset); IProtocolShareReserve(protocolShareReserve).releaseFunds(comptroller, assets); if (amount > asset.balanceOf(address(this))) { IPrimeLiquidityProvider(primeLiquidityProvider).releaseFunds(address(asset)); unreleasedPLPIncome[underlying] = 0; } } asset.safeTransfer(user, amount); emit InterestClaimed(user, vToken, amount); return amount; }
#0 - c4-pre-sort
2023-10-07T02:02:09Z
0xRobocop marked the issue as low quality report