This repository has been archived by the owner on Jun 11, 2023. It is now read-only.
xiaoming90 - Curve vault will undervalue or overvalue the LP Pool tokens if it comprises tokens with different decimals #8
Labels
High
A valid High severity issue
Reward
A payout will be made for this issue
Sponsor Confirmed
The sponsor acknowledged this issue is valid
xiaoming90
high
Curve vault will undervalue or overvalue the LP Pool tokens if it comprises tokens with different decimals
Summary
A Curve vault that comprises tokens with different decimals will undervalue or overvalue the LP Pool tokens. As a result, users might be liquidated prematurely or be able to borrow more than they are allowed. Additionally, the vault settlement process might break.
Vulnerability Detail
The
TwoTokenPoolUtils._getTimeWeightedPrimaryBalance
function, which is utilized by the Curve vault, is used to compute the total value of the LP Pool tokens (poolClaim
) denominated in the primary token.https://github.com/sherlock-audit/2023-02-notional/blob/main/leveraged-vaults/contracts/vaults/common/internal/pool/TwoTokenPoolUtils.sol#L67
If a leverage vault supports a Curve Pool that contains two tokens with different decimals, the math within the
TwoTokenPoolUtils._getTimeWeightedPrimaryBalance
function would not work, and the value returned from it will be incorrect. Consider the following two scenarios:If primary token's decimals (e.g. 18) > secondary token's decimals (e.g. 6)
To illustrate the issue, assume the following:
oraclePrice
within the function will be1 * 10^18
. Note that the oracle price is always scaled up to 18 decimals within the vault.The caller of the
TwoTokenPoolUtils._getTimeWeightedPrimaryBalance
function wanted to compute the total value of 50 LP Pool tokens.The
primaryBalance
will be50 DAI
.50 DAI
denominated in WEI will be50 * 10^18
since the decimals of DAI are 18.The
secondaryBalance
will be50 USDC
.50 USDC
denominated in WEI will be50 * 10^6
since the decimals of USDC are 6.Next, the code logic attempts to value the secondary balance (50 USDC) in terms of the primary token (DAI) using the oracle price (
1 * 10^18
).50 USDC should be worth 50 DAI (
50 * 10^18
). However, thesecondaryAmountInPrimary
shows that it is only worth 0.00000000005 DAI (50 * 10^6
).50 LP Pool tokens should be worth 100 DAI. However, the
TwoTokenPoolUtils._getTimeWeightedPrimaryBalance
function shows that it is only worth 50.00000000005 DAI, which undervalues the LP Pool tokens.If primary token's decimals (e.g. 6) < secondary token's decimals (e.g. 18)
To illustrate the issue, assume the following:
oraclePrice
within the function will be1 * 10^18
. Note that the oracle price is always scaled up to 18 decimals within the vault.The caller of the
TwoTokenPoolUtils._getTimeWeightedPrimaryBalance
function wanted to compute the total value of 50 LP Pool tokens.The
primaryBalance
will be50 USDC
.50 USDC
denominated in WEI will be50 * 10^6
since the decimals of USDC are 6.The
secondaryBalance
will be50 DAI
.50 DAI
denominated in WEI will be50 * 10^18
since the decimals of DAI are 18.Next, the code logic attempts to value the secondary balance (50 DAI) in terms of the primary token (USDC) using the oracle price (
1 * 10^18
).50 DAI should be worth 50 USDC (
50 * 10^6
). However, thesecondaryAmountInPrimary
shows that it is worth 50,000,000,000,000 USDC (50 * 10^18
).50 LP Pool tokens should be worth 100 USDC. However, the
TwoTokenPoolUtils._getTimeWeightedPrimaryBalance
function shows that it is worth 50 million USDC, which overvalues the LP Pool tokens.In summary, if a leverage vault has two tokens with different decimals:
TwoTokenPoolUtils._getTimeWeightedPrimaryBalance
function will undervalue the LP Pool tokensTwoTokenPoolUtils._getTimeWeightedPrimaryBalance
function will overvalue the LP Pool tokensImpact
A vault supporting tokens with two different decimals will undervalue or overvalue the LP Pool tokens.
The affected
TwoTokenPoolUtils._getTimeWeightedPrimaryBalance
function is called within theCurve2TokenPoolUtils._convertStrategyToUnderlying
function that is used for valuing strategy tokens in terms of the primary balance. As a result, the strategy tokens will be overvalued or undervaluedFollowing are some of the impacts of this issue:
Curve2TokenPoolUtils._convertStrategyToUnderlying
function is indirectly used for computing the collateral ratio of an account within Notional'sVaultConfiguration.calculateCollateralRatio
function.expectedUnderlyingRedeemed
is computed based on theCurve2TokenPoolUtils._convertStrategyToUnderlying
function. If theexpectedUnderlyingRedeemed
is incorrect, it will break the vault settlement process.Code Snippet
https://github.com/sherlock-audit/2023-02-notional/blob/main/leveraged-vaults/contracts/vaults/common/internal/pool/TwoTokenPoolUtils.sol#L67
Tool used
Manual Review
Recommendation
When valuing the secondary balance in terms of the primary token using the oracle price, the result should be scaled up or down the decimals of the primary token accordingly if the decimals of the two tokens are different.
The root cause of this issue is in the following portion of the code, which attempts to add the
primaryBalance
andsecondaryAmountInPrimary
before multiplying with theprimaryPrecision
. TheprimaryBalance
andsecondaryAmountInPrimary
might not be denominated in the same decimals. Therefore, they cannot be added together without scaling them if the decimals of two tokens are different.Consider implementing the following changes to ensure that the math within the
_getTimeWeightedPrimaryBalance
function work with tokens with different decimals. The below approach will scale the secondary token to match the primary token's precision before performing further computation.The
poolContext.primaryBalance
orpoolClaim
are not scaled up tostrategyContext.poolClaimPrecision
. Thus, theprimaryBalance
is not scaled in any form. Thus, I do not see the need to perform any conversion at the last line of the_getTimeWeightedPrimaryBalance
function.The following attempts to run through the examples in the previous section showing that the updated function produces valid results after the changes.
If primary token's decimals (e.g. 18) > secondary token's decimals (e.g. 6)
If primary token's decimals (e.g. 6) < secondary token's decimals (e.g. 18)
If primary token's decimals (e.g. 6) == secondary token's decimals (e.g. 6)
strategyContext.poolClaimPrecision
set toCurveConstants.CURVE_PRECISION
, which is1e18
.oraclePrice
is always in1e18
precision.The text was updated successfully, but these errors were encountered: