Lucene search

K
code423n4Code4renaCODE423N4:2023-09-VENUS-FINDINGS-ISSUES-549
HistoryOct 04, 2023 - 12:00 a.m.

Incorrect Score calculation in Prime.sol

2023-10-0400:00:00
Code4rena
github.com
vulnerability impact
proof of concept
code analysis
token decimals
security risk

7 High

AI Score

Confidence

High

Lines of code
<https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L872-L897&gt;
<https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/libs/Scores.sol#L1-L70&gt;

Vulnerability details

Impact

Score is not calculated correctly; improperly high weight will be given to the staked XVS amount or the supply/borrow amount. Rewards accrued will not be properly calculated, so users may accrue too much or too little reward.

Proof of Concept

Prime._calculateScore() and Prime._capitalForScore() calculate the values to be exponentiated according to the set alpha. The code is below:

    function _calculateScore(address market, address user) internal returns (uint256) {
        uint256 xvsBalanceForScore = _xvsBalanceForScore(_xvsBalanceOfUser(user));

        IVToken vToken = IVToken(market);
        uint256 borrow = vToken.borrowBalanceStored(user);
        uint256 exchangeRate = vToken.exchangeRateStored();
        uint256 balanceOfAccount = vToken.balanceOf(user);
        uint256 supply = (exchangeRate * balanceOfAccount) / EXP_SCALE;

        address xvsToken = IXVSVault(xvsVault).xvsAddress();
        oracle.updateAssetPrice(xvsToken);
        oracle.updatePrice(market);
   
        (uint256 capital, , ) = _capitalForScore(xvsBalanceForScore, borrow, supply, market);
        capital = capital * (10 ** (18 - vToken.decimals()));

        return Scores.calculateScore(xvsBalanceForScore, capital, alphaNumerator, alphaDenominator);
    }

    function _capitalForScore(
        uint256 xvs,
        uint256 borrow,
        uint256 supply,
        address market
    ) internal view returns (uint256, uint256, uint256) {
        address xvsToken = IXVSVault(xvsVault).xvsAddress();

        uint256 xvsPrice = oracle.getPrice(xvsToken);
        uint256 borrowCapUSD = (xvsPrice * ((xvs * markets[market].borrowMultiplier) / EXP_SCALE)) / EXP_SCALE;
        uint256 supplyCapUSD = (xvsPrice * ((xvs * markets[market].supplyMultiplier) / EXP_SCALE)) / EXP_SCALE;

        uint256 tokenPrice = oracle.getUnderlyingPrice(market);
        uint256 supplyUSD = (tokenPrice * supply) / EXP_SCALE;
        uint256 borrowUSD = (tokenPrice * borrow) / EXP_SCALE;

        if (supplyUSD &gt;= supplyCapUSD) {
            supply = supplyUSD &gt; 0 ? (supply * supplyCapUSD) / supplyUSD : 0;
        }

        if (borrowUSD &gt;= borrowCapUSD) {
            borrow = borrowUSD &gt; 0 ? (borrow * borrowCapUSD) / borrowUSD : 0;
        }

        return ((supply + borrow), supply, borrow);
    }

_capitalForScore() typically returns a value somewhat close to the sum of the supply and borrow parameters passed to it. This value is used to initialize the local variable capital in _calculateScore(). The supply and borrow parameters are equal to the amount of underlying supplied or borrowed by the user.

The problem stems from this line in _calculateScore():

        capital = capital * (10 ** (18 - vToken.decimals()));

This line is meant to scale up the value of capital to 18 decimal places of precision in case the underlying token has less than 18 decimals. This is so that the value of capital has the same precision as the value of xvsBalanceForScore, since XVS has 18 decimals. However, this line does not correctly implement the desired functionality because vTokens always have 8 decimals. Thus, this line of code always multiplies capital by 1e10.

This is important because the capital and xvsBalanceForScore values passed to Scores.calculateScore() should have the same amount of decimal precision in order to avoid improperly giving one of the arguments more weight. Scores.calculateScore() does not check for the decimal precision of its parameters, as this logic is (and should be) handled by Prime._calculateScore().

Screenshot showing that vTokens always have 8 decimals:
<https://gist.github.com/0xDetermination/86980a91377b6d4a097ac18ef82ed7a8&gt;

Example

Let’s consider the case where $\alpha=1/2$ and the market is wETH.

The function $f(x)=x^{1/2}$ can be used to calculate the post-exponentiation value of xvsBalanceForScore or capital (see the reward formula if the reason for exponentiation is unclear: <https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/README.md#rewards&gt;). However, capital is improperly multiplied by 1e10 since wETH already has 18 decimals of precision. Therefore, the function to calculate the post-exponentiation value of capital is actually $g(x)=({10}^{10}x)^{1/2}$, the derivative of which is $g’(x)=\frac{{10}^{5}}{{2x^{1/2}}}$. Comparatively, the derivative of $f(x)$ is $f’(x)=\frac{1}{2x^{1/2}}$. We can see that for somewhat equivalent values of xvsBalanceForScore and capital, a change in the user’s capital (supply/borrow amount) will affect the score significantly more than an equivalent change in the user’s xvsBalanceForScore (staked XVS amount).

It’s important for the capital and xvsBalanceForScore arguments passed to Scores.calculateScore() to have the same amount of decimal precision, or the score will be calculated incorrectly.

Furthermore, if the market is a token with six decimal places like USDC on ethereum mainnet/l2s, capital will be scaled up to 16 decimals places, 2 less than xvsBalanceForScore. In this case, the staked XVS amount will have an improperly high weight compared to the supply/borrow amount.

Recommended Mitigation Steps

The line of code capital = capital * (10 ** (18 - vToken.decimals())); should get the number of decimals of the underlying token instead of the number of decimals of the vToken.

Assessed type

Decimal


The text was updated successfully, but these errors were encountered:

All reactions

7 High

AI Score

Confidence

High