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: 32/115
Findings: 3
Award: $179.46
🌟 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
Issue | Description |
---|---|
[L-01] | estimateAPR can calculate slightly wrong APR |
[L-02] | sweepToken can break releaseFunds and accrueTokens |
[L-03] | calculateAPR will return overinflated values |
[L-04] | There is no current way to remove boosted markets |
[L-05] | getPendingInterests and _accrueInterestAndUpdateScore can run out of gas |
[L-06] | issue does not delete stakedAt mapping |
[N-01] | Admins can burn users Irrevocable tokens |
If estimateAPR is used by user who already has staked his balance into the system, estimateAPR will return a wrong and slightly lower APR. This is due to the system calculating a new user score and adding it to the already existing user scores. In totalScore
if the user has staked, his score is already accounted for, and when estimateAPR calculates his new score and adds it in, our user's score becomes counted twice. Thus inflating totalScore
value slightly which results in lower APR.
If sweepToken is called on a token that is used in tokenAmountAccrued[token_]
it's balance will get lower than tokenAmountAccrued[token_]
. This in tern will break every accounting that uses tokenAmountAccrued[token_]
.
Example :
tokenAmountAccrued[token_]
will be some amount.uint256 balance = IERC20Upgradeable(token_).balanceOf(address(this)); uint256 balanceDiff = balance - tokenAmountAccrued[token_];
uint256 accruedAmount = tokenAmountAccrued[token_]; tokenAmountAccrued[token_] = 0; IERC20Upgradeable(token_).safeTransfer(prime, accruedAmount);
If sweepToken is called on a currently used reward token it will break it's functionality and there will be no way to fix it. This is why it is best to have some protection against such things.
function sweepToken(IERC20Upgradeable token_, address to_, uint256 amount_) external onlyOwner { uint256 balance = token_.balanceOf(address(this)); if (amount_ > balance) { revert InsufficientBalance(amount_, balance); } + if(tokenAmountAccrued[token_] > 0){ + revert TokenInUse(token_); + } emit SweepToken(address(token_), to_, amount_); token_.safeTransfer(to_, amount_); }
calculateAPR and estimateAPR will return overinflated values due to wrong returns of PLP.
Flow: calculateAPR => _calculateUserAPR => _incomeDistributionYearly. _incomeDistributionYearly combines the earning of PSR and PLP, however the earnings of PLP will be wrong as when getEffectiveDistributionSpeed is called it will return tokenDistributionSpeeds[token_]
which is the fixed value of the income. While accrueTokens compares which values are lower:
And assigns the lower one.
uint256 balanceDiff = balance - tokenAmountAccrued[token_]; if (distributionSpeed > 0 && balanceDiff > 0) { uint256 accruedSinceUpdate = deltaBlocks * distributionSpeed; uint256 tokenAccrued = (balanceDiff <= accruedSinceUpdate ? balanceDiff : accruedSinceUpdate); tokenAmountAccrued[token_] += tokenAccrued;
This means that if sometimes balance
is lower than tokenDistributionSpeeds
the APR will be inflated, and with balance
being higher the APR to be underinflated.
Markets can only be added to the system without a way to remove them. For now this is not an issue as only well used and big markets will be added (BTC,ETH,USDT,USDC <- mentioned by the dev.), however in the future if smaller markets are included and they die off, it will be beneficial for them to be removed. To save gas on the loops with _allMarkets.length
and for easier management.
As all places where _allMarkets.length
is used can be DoS partly as the amount of markets can increase in the future. I would suggest to store and use only markets that a user participates in. This can be easily achieved by a single mapping where it is stored user => market
.
This inconsistency in issue](https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L331-L359 can cause some issues in the future or mess with the time mapping where the staking duration is stored. As one of the paths under issue](https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L331-L359 does not delete stakedAt[]
, like the other paths.
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]];//@audit L `stakedAt` not deleted } unchecked { i++; } } } else { for (uint256 i = 0; i < users.length; ) { _mint(false, users[i]); _initializeMarkets(users[i]); delete stakedAt[users[i]]; unchecked { i++; } } } }
In the code, and explained in discord it was stated that Irrevocable are not destroyable and not burnable, however the admins of the system are able to burn them. Of course this is not a major issue since the admins are not malicious, but it's gonna be useful to state it in the docs that if users misbehave admins are able to burn these tokens.
#0 - 0xRobocop
2023-10-07T03:07:19Z
L-02 dup of #42 L-04 dup of #421 L-06 dup of #633
#1 - c4-pre-sort
2023-10-07T03:07:27Z
0xRobocop marked the issue as sufficient quality report
🌟 Selected for report: DavidGiladi
Also found by: 0x3b, 0xWaitress, 0xhacksmithh, 0xprinc, hihen, jkoppel, lsaudit, oakcobalt, pavankv, pontifex
15.6862 USDC - $15.69
Issue | Description |
---|---|
[G-01] | In updateScores you don't need to accrue every market for every user |
[G-02] | No need to save totalIncomePerBlockFromMarket |
[G-03] | Use block.number instead of a function |
In updateScores the internal _executeBoost is called where for every user every market is accrued. However this is not necessary since every market needs to be accrued once. This will save a lot of gas since for a transaction with 20 users, all markets are gonna be accrued 20 times, while only 1 accrue is needed per market.
I would suggest to first accrue all of the markets, and then add and sync it with _interestAccrued. To do this you will need to add a for loop to updateScores to accrue all markets and then you can modify _executeBoost to not accrue any market.
function _executeBoost(address user, address vToken) internal { if (!markets[vToken].exists || !tokens[user].exists) {return;} - accrueInterest(vToken); interests[vToken][user].accrued += _interestAccrued(vToken, user); interests[vToken][user].rewardIndex = markets[vToken].rewardIndex; }
Under _incomeDistributionYearly totalIncomePerBlockFromMarket
is saved and used in one place, there is no need for such variable and you can insert the function in the math logic. Same can be said for incomePerBlockForDistributionFromMarket
, as again it is used only in amount = BLOCKS_PER_YEAR * incomePerBlockForDistributionFromMarket;
- uint256 totalIncomePerBlockFromMarket = _incomePerBlock(vToken); - uint256 incomePerBlockForDistributionFromMarket = (totalIncomePerBlockFromMarket * _distributionPercentage()) / IProtocolShareReserve(protocolShareReserve).MAX_PERCENT(); + uint256 incomePerBlockForDistributionFromMarket = (_incomePerBlock(vToken) * _distributionPercentage()) / IProtocolShareReserve(protocolShareReserve).MAX_PERCENT();
block.number
instead of a functionUnder _initializeToken there is no need to call getBlockNumber as just the EVM call will cost less gas.
#0 - 0xRobocop
2023-10-06T00:15:07Z
Interesting:
G-01 G-02
#1 - c4-pre-sort
2023-10-06T00:15:13Z
0xRobocop marked the issue as sufficient quality report
#2 - c4-judge
2023-11-02T19:04:36Z
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
159.397 USDC - $159.40
The Venus Prime program is designed to incentivize liquidity providers and borrowers to stake XVS. This is achieved through the use of the Prime token. To participate in the Prime program, you need to:
Once these conditions are met, Venus will incentivize you with consistent rewards in the markets you participate in (borrowing/supplying).
The program primarily functions with two methods:
Through these two methods, the Prime program encourages liquidity providers and borrowers to purchase and stake XVS, thereby increasing its price and overall value in the meantime.
Severity | Occurrences |
---|---|
High | 3 |
Medium | 3 |
Low | 6 |
Gas | 3 |
Analisys | 6 hours |
Start date | 28.09.2023 |
End date | 03.10.2023 |
Total | 5 days |
Members | Positions | Time spent |
---|---|---|
0x3b | full time researcher | ~30H |
The codebase was "a blast," and the time spent was just perfect. I had enough time to delve into every aspect of the scope, even exploring some out-of-scope codebases to gather reference points. The economics were truly fascinating, and I examined them from every perspective. In fact, approximately 80% of my time was dedicated to attempting to manipulate the rewards, user's score, and interest. I encountered several scenarios where any user could potentially cause damage and extract value. Although most of the bugs that I found were in the Yearly APR calculations, which are view functions used only for the front end. Even though no funds were at risk, these functions were quite buggy and could cause some issues with the calculations and the overall system.
The code quality was excellent, having undergone four previous audits, one of which was conducted by OZ. However, it contained some unnecessary functions that only contributed to increased gas consumption during deployments. The scoring and interest components were highly sophisticated, but due to numerous missing pieces of information related to how vTokens work, I found myself investing more time than intended in out-of-scope code exploration and extensive documentation reading.
Code Comments Most comments were good, but there's always room for improvement. In some parts, especially in the Interface contracts, making comments and explanations clearer would help developers work with the code more easily. Adding more detailed comments and documentation for complex or important parts of the code can make the whole codebase easier to understand and maintain. This would benefit not only the current development team but also make it simpler for future contributors to work with the existing code.
Documentation The documentation was well-crafted, providing a comprehensive explanation of the system. The mathematical equations were the exceptional feature, as they provided insight into how the system operates beneath the surface.
Organization Codebase is very mature and well organized with clear distinctions between the 6 contracts, and their respective interfaces.
Testing
Unit Tests: The unit tests were sophisticated enough to uncover basic bugs. However, I have noticed a few issues with them:
Prime.sol
The Prime contract is the main contract for the update, handling all updates related to scores and interest in the Prime program. It is accompanied by Protocol Shared Reserve (PSR) and Prime Liquidity Provider (PLP), which are two contracts that manage external income. Under Prime, there are several implemented features:
With the mechanisms mentioned above, "Prime.sol" controls the Prime incentives and is used for the main user flow.
To participate, you will need to:
PrimeStorage.sol
Prime Storage is the storage contract used by Prime. There are no functions inside it; it contains only regular storage. It includes the storage of some global variables, mappings for scores and interest, as well as structs to manage users and markets.
PrimeLiquidityProvider.sol
Prime Liquidity Provider is the contract helper of Prime, as it's one of the two contracts that can supply Prime with tokens (the other one being PSR, although it is out of scope). With this contract:
Scores.sol
Scores is the math contract used to calculated a given user's score. It uses a special math function called - Cobb-Douglas - which is described in the docs as:
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
$FixedMath.sol
FixedMath involves some simplified yet accurate mathematical calculations. It is used to multiply and divide values while ensuring that the inputs are correct according to the formula being used.
FixedMath0x.sol
FixedMath0x is a more complex version of FixedMath. It is used to calculate intricate mathematical problems, such as the "natural logarithm of a fixed-point number" and the "natural exponent for a fixed-point number," which are challenging to compute using regular code. That's why FixedMath0x primarily employs assembly bit representation of numbers.
The contracts provided to us were sufficient to achieve the system's intended goals. They included interest acrural, user scores and (mostly) math calculators. They were relatively simple, and the code was straightforward, making the audit a pleasant experience.
Positive Suggestions:
Some things that may warrant a second look from the devs:
Venus demonstrates an exceptionally high level of decentralization. This decentralization paves the way for a brighter future where all users are empowered to shape the ecosystem as they see fit.
However, on the other note, centralization may not be a major concern, provided that these admins are genuinely invested in the project's overall growth. When harnessed effectively, centralization can provide the system with a competitive edge over its competitors. It facilitates rapid decision-making, enabling Venus to adapt swiftly and stay ahead of industry trends. Making it less of a corporate gian and ore of a 3 man start-up. In contrast, decentralized DAO systems often require proposals, votes, and lengthy approval and implementation processes.
Nevertheless, it's crucial to acknowledge that centralization also carries potential downsides, such as an increased vulnerability to single points of failure. Striking a balance between centralization and decentralization is important to ensure the system's robustness and sustainability.
The current state of this protocol leaves it susceptible to various types of economic attacks. Certain functions within the code can be manipulated by users to gain advantages over others or even illicitly acquire tokens from the protocol. However, after a comprehensive refinement following this audit, the risks associated with these vulnerabilities can be significantly reduced. By addressing these weaknesses and implementing robust security measures, the protocol can become more resilient against potential economic attacks.
Nonetheless, it is crucial to exercise caution when adding new features to the code, especially those involving economic incentives. While they can enhance the protocol's performance and attract users, they also tend to be the most vulnerable points, susceptible to exploitation if not designed and implemented with the utmost care.
Some potential risks may include:
In general, the Venus project exhibits an interesting and well-developed architecture I believe the team has done a good job regarding the code, but the identified risks need to be addressed, and measures should be implemented to protect the protocol from potential malicious use cases. It is also highly recommended that the team continues to invest in security measures such as mitigation reviews, audits, and bug bounty programs to maintain the security and reliability of the project.
30 hours
#0 - c4-pre-sort
2023-10-07T06:22:09Z
0xRobocop marked the issue as sufficient quality report
#1 - c4-judge
2023-11-03T20:11:33Z
fatherGoose1 marked the issue as grade-a