Venus Prime - joaovwfreire's results

Earn, borrow & lend on the #1 Decentralized Money Market on the BNB chain.

General Information

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

Venus Protocol

Findings Distribution

Researcher Performance

Rank: 94/115

Findings: 1

Award: $4.37

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L970-L979 https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L40

Vulnerability details

Impact

At the _incomeDistributionYearly function, the Prime contract utilizes a BLOCKS_PER_YEAR constant defined on deployment that will account for the amount of blocks per year at the deployed chain.

function _incomeDistributionYearly(address vToken) internal view returns (uint256 amount) {
        uint256 totalIncomePerBlockFromMarket = _incomePerBlock(vToken);
        uint256 incomePerBlockForDistributionFromMarket = (totalIncomePerBlockFromMarket * _distributionPercentage()) /
            IProtocolShareReserve(protocolShareReserve).MAX_PERCENT();
        amount = BLOCKS_PER_YEAR * incomePerBlockForDistributionFromMarket;

        uint256 totalIncomePerBlockFromPLP = IPrimeLiquidityProvider(primeLiquidityProvider)
            .getEffectiveDistributionSpeed(_getUnderlying(vToken));
        amount += BLOCKS_PER_YEAR * totalIncomePerBlockFromPLP;
    }

This value is then utilized for calculation of the APR. The issue is there is no deterministic way to predict the amount of blocks a year will have due to the nature of block validation and leap years.

The calculated APR value will have different imprecision values depending on the chain: at chains with higher block rates, the impact should be bigger, as shown at the proof of concept. Even though the function that utilizes this variable is view-only (does not modify the chain's state), the risk lies on false claims that a front-end could display and other protocols could attempt to integrate this function creating risks if the purpose is composability. Moreover, the Prime system is made to create value for the XVS token and by displaying wrong values, taking part on such a system may become less attractive.

Proof of Concept

This source code is not in scope but illustrates that the team has once implemented a constant for the amount of blocks per year at the BNB chain at the @venusprotocol/isolated-pools package:

/// @dev The approximate number of blocks per year that is assumed by the interest rate model
uint256 constant BLOCKS_PER_YEAR = 10_512_000;

This constant is based on the assumption that the BNB chain will have 3 second block times - therefore running 28800 blocks per day -> 10512000 blocks per year.

We can draw the assumption that the team will utilize constants to determine the amounts of block per year. In a production setting, however, the amount of blocks per year is not a constant, as shown by the following data:

Amount of blocks that has paased (measured till October 1st) on the Ethereum, BNB and Arbitrum:

From Ethereum Block Count and Rewards Chart:

YearTotal blocksAvg blocks per day
2015778483Not considered
201621400355863.109589
201719202795261.038356
201821559295906.654795
201922046516040.139726
202023717536497.953425
202123510366441.194521
202223920246553.490411
202319366157172.648148

From BNB Smart Chain Block Count and Rewards Chart:

YearTotal blocksAvg blocks per dayBlocks lost per dayAvg blocks lost per yearDays not accounted for
2020359331828746.54453.45619564.8960.679336667
20211037634328428.33699371.66301371356574.7103125
20221042398928558.87397241.1260274880113.0559375
2023778582128729.9667970.0332103325562.121770.887573673

From Arbitrum Smart Chain Block Count and Rewards Chart:

YearTotal blocksAvg blocks per day
20214221288Not considered
202245849623125615.4055
202385848543316784.2915

As shown above, the block amounts are very hard to predict and will lead to considerable imprecision on the APR calculations for staking positions on the smaller end, even on BNB, which has a relatively stable block rate. Consider the graph from the documentation and the following reduction: Venus APR

Let:IMB:Income per block from the marketP:Distribution percentage (_distributionPercentage())M:Maximum percentage allowed by the protocolIPB:Income per block from the PLPB:Number of blocks in a year (BLOCKS_PER_YEAR)\text{Let:} \begin{align*} I_{MB} & : \text{Income per block from the market} \\ P & : \text{Distribution percentage (\_distributionPercentage())} \\ M & : \text{Maximum percentage allowed by the protocol} \\ I_{PB} & : \text{Income per block from the PLP} \\ B & : \text{Number of blocks in a year (BLOCKS\_PER\_YEAR)} \end{align*}
The income per block for distribution from the market is: IMDB=IMB×PM\text{The income per block for distribution from the market is: } I_{MDB} = \frac{I_{MB} \times P}{M}
The yearly income distribution from the market is: YM=B×IMDB\text{The yearly income distribution from the market is: } Y_{M} = B \times I_{MDB}
The total yearly distribution amount is:YT=YM+YP\text{The total yearly distribution amount is:} Y_{T} = Y_{M} + Y_{P}
Substituting in the values forYMandYP:YT=B×(IMB×PM)+B×IPB\text{Substituting in the values for} Y_{M} \text{and} Y_{P}: Y_{T} = B \times \left( \frac{I_{MB} \times P}{M} \right) + B \times I_{PB}
Reducing, we see that the BLOCKS_PER_YEAR is a common factor:YT=B×(IMB×PM+IPB)\text{Reducing, we see that the BLOCKS\_PER\_YEAR is a common factor:} Y_{T} = B \times \left( \frac{I_{MB} \times P}{M} + I_{PB} \right)

The graph shows that positions close to the 1000 XVS range, will earn around 2.5-3% APR. On a not so bad case, consider BNB in 2021. The imprecision would lead to 4.7 days not accounted for, approximately 1,28% of the year. This means the view function would display to users 1.28% more APR than the actual value. On a worst case scenario, consider a contract deployed on Arbitrum in the beginning of 2023. The imprecision would lead to extra 191196 blocks accounted for per day. That would lead to a decrease of over 2/3 on the APR displayed to users. That would be a 2.5-3% APR turned to 0.75-1%.

Tools Used

Manual review

The team should make the BLOCKS_PER_YEAR variable dynamic, so that it can be updated as the chain evolves. Additionally, a disclaimer should be added to the documentation, warning users that the APR displayed is an estimate and may not reflect the actual value.

Assessed type

Math

#0 - c4-pre-sort

2023-10-06T01:38:23Z

0xRobocop marked the issue as duplicate of #76

#1 - c4-judge

2023-10-31T20:42:29Z

fatherGoose1 changed the severity to QA (Quality Assurance)

#2 - c4-judge

2023-11-03T02:06:52Z

fatherGoose1 marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter