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: 36/115
Findings: 2
Award: $129.33
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: deth
Also found by: 0xDetermination, 0xpiken, 3agle, Brenzee, Flora, HChang26, KrisApostolov, Satyam_Sharma, Testerbot, aycozynfada, berlin-101, gkrastenov, mahdirostami, merlin, rokinot, rvierdiiev, said, santipu_, sl1, tapir, twicek
124.9633 USDC - $124.96
https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/Prime.sol#L331-L359 https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/Prime.sol#L397-L405 https://github.com/code-423n4/2023-09-venus/blob/b11d9ef9db8237678567e66759003138f2368d23/contracts/Tokens/Prime/Prime.sol#L407-L414
When an admin burns the prime token of a user, there is a possibility for this user to claim a new prime token without waiting for the staking period.
There are 2 possible ways for a user to get a revocable prime token, if an admin calls issue
:
function issue(bool isIrrevocable, address[] calldata users) external { _checkAccessAllowed("issue(bool,address[])"); if (isIrrevocable) { for (uint256 i = 0; i < users.length; ) { Token storage userToken = tokens[users[i]]; if (userToken.exists && !userToken.isIrrevocable) { _upgrade(users[i]); } else { _mint(true, users[i]); _initializeMarkets(users[i]); } unchecked { i++; } } } else { for (uint256 i = 0; i < users.length; ) { _mint(false, users[i]); _initializeMarkets(users[i]); delete stakedAt[users[i]]; unchecked { i++; } } } }
Or if the user himself calls claim
:
Prime.sol#L397-L405
function claim() external { if (stakedAt[msg.sender] == 0) revert IneligibleToClaim(); if (block.timestamp - stakedAt[msg.sender] < STAKING_PERIOD) revert WaitMoreTime(); stakedAt[msg.sender] = 0; _mint(false, msg.sender); _initializeMarkets(msg.sender); }
There is only one way for a user to get an irrevocable token which is if an admin calls issue
for this user.
The problem arises when a user gets an irrevocable prime token through issue
while having a set block.timestamp
in his stakedAt
mapping. In this case the user could mint a prime token itself after waiting the STAKING_PERIOD
, but let's say that an admin grants him an irrevocable prime token before he calls claim
. Later, if the admin (or community) decides to burn the token of this user, he will be able to mint a new revocable token right away without waiting the STAKING_PERIOD
because his stakedAt
mapping has not been reset to zero and still has the block.timestamp
that has been set prior to the admin granting him the prime token.
PS: Even irrevocable tokens are burnable by the admin, as per the comments:
/** * @notice For burning any prime token * @param user the account address for which the prime token will be burned */ function burn(address user) external { _checkAccessAllowed("burn(address)"); _burn(user); }
Users with an irrevocable token who get their prime tokens burned by an admin can bypass the staking period if they have a positive stakedAt
at the time of issuance.
Manual Review
Consider deleting the stakedAt
also for irrevocable prime token issuance.
function issue(bool isIrrevocable, address[] calldata users) external { _checkAccessAllowed("issue(bool,address[])"); if (isIrrevocable) { for (uint256 i = 0; i < users.length; ) { Token storage userToken = tokens[users[i]]; if (userToken.exists && !userToken.isIrrevocable) { _upgrade(users[i]); } else { _mint(true, users[i]); _initializeMarkets(users[i]); + delete stakedAt[users[i]]; } unchecked { i++; } } } else { for (uint256 i = 0; i < users.length; ) { _mint(false, users[i]); _initializeMarkets(users[i]); delete stakedAt[users[i]]; unchecked { i++; } } } }
Other
#0 - c4-pre-sort
2023-10-06T22:06:21Z
0xRobocop marked the issue as duplicate of #633
#1 - c4-judge
2023-11-02T01:01:04Z
fatherGoose1 marked the issue as satisfactory
#2 - c4-judge
2023-11-05T00:50:32Z
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
Issue ID | Title | Impact | Recommended Mitigation Steps | Code Reference |
---|---|---|---|---|
L-01 | No remove market function | DoS | Consider adding a remove market function. | Prime.sol#L607-L617 |
L-02 | updateMultipliers does not have bounds checks for supplyMultiplier and borrowMultiplier | Limits chance of admin error | Consider adding bounds limit for supplyMultiplier and borrowMultiplier . | Prime.sol#L263-L280 |
L-03 | Consider documenting the processus that prevents the non-issue described in the code README under "Potential Locked Tokens" | Potential for tokens to become stuck in contract if not clearly documented | Clarify in documentation and comments the process allowing to prevent this issue. | Prime.sol#L583-L586 |
Markets can be added through addMarket
but cannot be removed. Over time the allMarkets
array length might become too high to perform certain calls like _accrueInterestAndUpdateScore
:
function _accrueInterestAndUpdateScore(address user) internal { address[] storage _allMarkets = allMarkets; for (uint256 i = 0; i < _allMarkets.length; ) { _executeBoost(user, _allMarkets[i]); _updateScore(user, _allMarkets[i]); unchecked { i++; } } }
PS: This issue is present in the bot report but with a poor description so I'm adding it here anyway.
DoS
Consider adding a remove market function.
updateMultipliers
does not have bounds checks for supplyMultiplier
and borrowMultiplier
See title.
function updateMultipliers(address market, uint256 supplyMultiplier, uint256 borrowMultiplier) external { _checkAccessAllowed("updateMultipliers(address,uint256,uint256)"); if (!markets[market].exists) revert MarketNotSupported(); accrueInterest(market); emit MultiplierUpdated( market, markets[market].supplyMultiplier, markets[market].borrowMultiplier, supplyMultiplier, borrowMultiplier ); markets[market].supplyMultiplier = supplyMultiplier; markets[market].borrowMultiplier = borrowMultiplier; _startScoreUpdateRound(); }
Having bounds for these parameters will limit the chance of an admin error.
Consider adding bounds for supplyMultiplier
and borrowMultiplier
.
Potential Locked Tokens According to the logic of function accrueInterest(), it is possible that some rewards will not be collected by >any user. If rewards are currently being issued, but no user has a positive score, then no user can collect >these rewards. Even so, all currently unreleased funds issued can be sent from both PrimeLiquidityProvider and >ProtocolShareReserve to the Prime contract by anyone. Since no user is privy to these funds, they will become >stuck in the contract.
Prime tokens will be issued at the same time (same transaction) the Prime contracts are enabled, so the described scenario will not happen.
It is not clear what enabling Prime contracts means precisely. If it doesn't mean that admins will wait to provide rewards until there is at least one user with a positive score, it will lead to the issue described.
uint256 delta; if (markets[vToken].sumOfMembersScore > 0) { delta = ((distributionIncome * EXP_SCALE) / markets[vToken].sumOfMembersScore); }
If enabling markets solely means to deploy the Prime contract and add markets without having any user with a positive score it could lead to the issue described.
Consider clarifying in your documentation and in comments the processus allowing to prevent the issue as it's an important vulnerability surface.
#0 - 0xRobocop
2023-10-07T02:23:09Z
L-01 Dup of #421
#1 - c4-pre-sort
2023-10-07T02:23:14Z
0xRobocop marked the issue as low quality report
#2 - c4-judge
2023-11-03T02:38:39Z
fatherGoose1 marked the issue as grade-b