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: 53/115
Findings: 3
Award: $53.88
π Selected for report: 0
π Solo Findings: 0
π Selected for report: 0xDetermination
Also found by: 0xblackskull, 0xweb3boy, ADM, Breeje, Pessimistic, PwnStars, SBSecurity, Satyam_Sharma, ThreeSigma, al88nsk, blutorque, debo, dethera, maanas, neumo, oakcobalt, pina, said, sces60107, tapir, tsvetanovv, xAriextz
32.2731 USDC - $32.27
https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L200-L230 https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L208
Scores for the subsequent users will not be updated if the scores for one of the users is already updated.
https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L200-L230
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); } }
in the above function the scores are updated in a loop based on if (isScoreUpdated[nextScoreUpdateRoundId][user]) continue;
this condition but if there exists a user for whom scores are already update and the bool value is already set to true
then due to the continue
the first for loop will not execute the below mentioned code and without incrementing the loop it will again go back to the same iteration and hence falling an infinite loop and thus breaking the function use.
and this will also not update the market scores which is done in the next for loop this certainly breaks the whole functions and the core methodology for the protocol.
address[] storage _allMarkets = allMarkets; for (uint256 j = 0; j < _allMarkets.length; ) { address market = _allMarkets[j]; _executeBoost(user, market); _updateScore(user, market); unchecked { j++; } }
Manual Review
The solution for this issue would be to either manually increment the "i" when we "continue" or to simply use the default "for" loop syntax without the gas opt
DoS
#0 - c4-pre-sort
2023-10-06T01:24:29Z
0xRobocop marked the issue as duplicate of #556
#1 - c4-judge
2023-10-31T19:22:51Z
fatherGoose1 marked the issue as satisfactory
#2 - c4-judge
2023-11-05T00:40:03Z
fatherGoose1 changed the severity to 2 (Med 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
https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/PrimeLiquidityProvider.sol#L254 https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/PrimeLiquidityProvider.sol#L276-L278
TokensAccrued on Arbitrum or Optimism will result in wrong accrual
function accrueTokens(address token_) public { _ensureZeroAddress(token_); _ensureTokenInitialized(token_); uint256 blockNumber = getBlockNumber(); uint256 deltaBlocks = blockNumber - lastAccruedBlock[token_]; if (deltaBlocks > 0) { uint256 distributionSpeed = tokenDistributionSpeeds[token_]; uint256 balance = IERC20Upgradeable(token_).balanceOf(address(this)); uint256 balanceDiff = balance - tokenAmountAccrued[token_]; if (distributionSpeed > 0 && balanceDiff > 0) { uint256 accruedSinceUpdate = deltaBlocks * distributionSpeed; uint256 tokenAccrued = (balanceDiff <= accruedSinceUpdate ? balanceDiff : accruedSinceUpdate); tokenAmountAccrued[token_] += tokenAccrued; emit TokensAccrued(token_, tokenAccrued); } lastAccruedBlock[token_] = blockNumber; } }
The above function is used to Accrue token by updating the distribution state and it is using uint256 blockNumber = getBlockNumber();
getBlockNumber() for doing the further state changes and the getBlockNumber() is defined as:
function getBlockNumber() public view virtual returns (uint256) { return block.number; }
While this structure will work on mainnet, it is problematic for use on Arbitrum. According to Arbitrum Docs block.number returns the most recently synced L1 block number. Once per minute the block number in the Sequencer is synced to the actual L1 block number. This period could be abused to completely bypass this protection. The user would open their position 1 Arbitrum block before the sync happens, the close it the very next block. It would appear that there has been 5 block (60 / 12) since the last transaction but in reality it has only been 1 Arbitrum block. Given that Arbitrum has 2 seconds blocks I would be impossible to block this behavior through parameter changes.
It also presents an issue for Optimism because each transaction is it's own block.
Manual Review
The delay should be measured using block.timestamp rather than block.number
Timing
#0 - c4-pre-sort
2023-10-05T23:24:16Z
0xRobocop marked the issue as duplicate of #132
#1 - c4-judge
2023-10-31T19:34:39Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#2 - c4-judge
2023-11-03T01:37:44Z
fatherGoose1 marked the issue as grade-b
π Selected for report: 3agle
Also found by: 0x3b, 0xDetermination, 0xweb3boy, ArmedGoose, Bauchibred, Breeje, J4X, hunter_w3b, jamshed, kaveyjoe, oakcobalt, radev_sw, versiyonbir
17.244 USDC - $17.24
List | Head | Details |
---|---|---|
1 | Overview of Venus Prime | overview of the key components and features of Venus Prime |
2 | Audit approach | Process and steps I followed |
3 | Learnings | Learnings from this protocol |
4 | Possible Systemic Risks | The possible systemic risks based on my analysis |
5 | Code Commentary | Suggestions for existing code base |
6 | Centralization risks | Concerns associated with centralized systems |
7 | Risks as per Analysis | Possible risks |
8 | Non-functional aspects | General suggestions |
9 | Time spent on analysis | The Over all time spend for this reports |
Venus Prime
is a revolutionary incentive program aimed to bolster user engagement and growth within the
protocol. Venus Prime aims to enhance yields and promote $XVS staking, focusing on markets including USDT,
BNB, BTC and ETH.
Venus Prime essentials
Venus Prime's uniqueness lies in its self-sustaining rewards system, instead of external sources, rewards are derived from the protocol's revenue, fostering a sustainable and ever-growing program.
Eligible $XVS holders will receive a unique, non-transferable Soulbound Token, which boosts rewards across selected markets.
Prime tokens
Venus Prime encourages user commitment through two unique Prime Tokens:
Users need to stake at least 1,000 XVS for 90 days in a row. After these 90 days, users can mint their Prime Token. If a user decides to withdraw XVS and their balance falls below 1,000 XVS, their Prime Token will be automatically revoked.
It is yet to be defined in the future.
Venus Prime
aims to incentivize larger stake sizes and diverse user participation. This is expected to significantly increase the staking of XVS, the Total Value Locked (TVL), and market growth.
Venus Prime
intends to promote user loyalty and the overall growth of the protocol. By endorsing long-term staking, discouraging premature withdrawals, and incentivizing larger stakes, Venus Prime sets a new course in user engagement and liquidity, contributing to Venus Protocol's success.
User needs to Stake their $XVS tokens to be eligible for Venus Prime.
Rewards Mechanism
Venus Prime
is using Cobb-Douglas
function to calculate scores and rewards for users,the CobbβDouglas production function is a particular functional form of the production function, widely used to represent the technological relationship between the amounts of two or more inputs (particularly physical capital and labor) and the amount of output that can be produced by those inputs.
Reward Formula: Cobb-Douglas function
Where:
Rewards_{i,m}
$ = Rewards for user $i
$ in market $m
$\Gamma_m
$ = Protocol Reserve Revenue for market $m
$ΞΌ
$ = Proportion to be distributed as rewardsΞ±
$ = Protocol stake and supply & borrow amplification weightΟ_{i}
$ = XVS staked amount for user $i
$\sigma_i
$ = Sum of qualified supply and borrow balance for user $i
$β_{j,m}
$ = Sum for all users $j
$ in markets $m
$Qualifiable XVS Staked:
Qualifiable supply and borrow:
A higher value of Ξ± increases the weight on stake contributions in the determination of rewards and decreases the weight on supply/borrow contributions. The value of Ξ± is between 0-1
A default weight of 0.5 weight has been evaluated as a good ratio and is not likely to be changed. A higher value will only be needed to attract more XVS stake from the Prime token holders at the expense of supply/ borrow rewards.
The Prime
contract takes the total released + unreleased funds and distributes to Prime token holders each block. The distribution is proportional to the score of the Prime token holders.
When a user claims their rewards and if the contract doesnβt have enough funds then Prime triggers the release of funds from PSR to Prime
contract in the same transaction i.e., in the claim
function.
I followed below steps while analyzing and auditing the code base.
Analyzed the over all codebase one iterations very fast
Study of documentation to understand each contract purposes, its functionality, how it is connected with other contracts, etc.
Then i read old audits and already known findings. Then went through the bot races findings
Then I started to go through my second iteration and this time function by function and line by line understanding the whole protocol deeply and took the necessary notes to ask some questions to sponsors.
The first stage of the audit
During the initial stage of the audit for Venus Prime I focused on understanding the code and getting familiar with the code and finding any low hanging issues in the codebase of Venus Prime, also this stage of my audit let me focus on finding QA's or GAS optimisation issues in the codebase of Venus prime.
The second stage of the audit
In the second stage of the audit for Venus Prime my main focus shifted from finding the low hanging bugs to finding more protocol related bugs or logic related bugs and also understanding the code of Venus Prime in depth, This involved identifying and analyzing the importand functions in the Prime.sol contract and PrimeLiquidityProvider.sol contract. By examining these two contracts the audit aimed to gain a comprehensive understanding of the protocol's functionality and potential risks.
The third stage of the audit
During the third stage of the audit for Venus Prime, my main focus was thoroughly examining and marking any doubtful or vulnerable areas within the protocol. This stage involved conducting comprehensive vulnerability assessment and identifying potential weaknesses in the system. I found many vulnerable
code parts and marked them with @audit tags
.
The fourth stage of the audit
During the fourth stage of the audit for Venus Prime, a comprehensive analysis and testing of the previously identified doubtful and vulnerable areas were conducted. This stage involved diving deeper into these areas, performing in-depth examinations, and subjecting them to rigorous testing, including fuzzing with various inputs. Finally concluded findings after all research's and tests. Then i reported C4 with proper formats
A single point of failure is not acceptable for this project Centrality risk is high in the project, the role of onlyOwner
detailed below has very critical and important powers
Wrong Prime token may be set by a malicious or stolen private key onlyOwner
msg.sender
File: contracts/Tokens/Prime/PrimeLiquidityProvider.sol ```function setPrimeToken(address prime_) external onlyOwner { _ensureZeroAddress(prime_); emit PrimeTokenUpdated(prime, prime_); prime = prime_; }
File: contracts/Tokens/Prime/PrimeLiquidityProvider.sol function initializeTokens(address[] calldata tokens_) external onlyOwner { for (uint256 i; i < tokens_.length; ) { _initializeToken(tokens_[i]); unchecked { ++i; } } }
File: contracts/Tokens/Prime/PrimeLiquidityProvider.sol function sweepToken(IERC20Upgradeable token_, address to_, uint256 amount_) external onlyOwner { uint256 balance = token_.balanceOf(address(this)); if (amount_ > balance) { revert InsufficientBalance(amount_, balance); } emit SweepToken(address(token_), to_, amount_); token_.safeTransfer(to_, amount_); }
15 Hours
15 hours
15 hours
#0 - 0xRobocop
2023-10-07T06:17:58Z
Half of the analysis is just project docs
#1 - c4-pre-sort
2023-10-07T06:18:02Z
0xRobocop marked the issue as low quality report
#2 - c4-judge
2023-11-03T20:16:22Z
fatherGoose1 marked the issue as grade-b