This repository has been archived by the owner on May 26, 2023. It is now read-only.
xiaoming90 - Two token vault will be broken if it comprises tokens with different decimals #18
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
Will Fix
The sponsor confirmed this issue will be fixed
xiaoming90
high
Two token vault will be broken if it comprises tokens with different decimals
Summary
A two token vault that comprises tokens with different decimals will have many of its key functions broken. For instance, rewards cannot be reinvested and vault cannot be settled.
Vulnerability Detail
The
Stable2TokenOracleMath._getSpotPrice
function is used to compute the spot price of two tokens.https://github.com/sherlock-audit/2022-12-notional/blob/main/contracts/vaults/balancer/internal/math/Stable2TokenOracleMath.sol#L15
Two tokens (USDC and DAI) with different decimals will be used below to illustrate the issue:
USDC/DAI Spot Price
Assume that the primary token is DAI (18 decimals) and the secondary token is USDC (6 decimals). As such, the scaling factors would be as follows. The token rate is ignored and set to 1 for simplicity.
Primary Token (DAI)'s scaling factor = 1e18
Secondary Token (USDC)'s scaling factor = 1e30
Assume that the
primaryBalance
is 100 DAI (100e18), and thesecondaryBalance
is 100 USDC (100e6). Line 25 - 28 of the_getSpotPrice
function will normalize the tokens balances to 18 decimals as follows:scaledPrimaryBalance
will be 100e18 (It remains the same as no scaling is needed because DAI is already denominated in 18 decimals)scaledSecondaryBalance
will upscale to 100e18The
StableMath._calcSpotPrice
function at Line 39 returns the spot price of Y/X. In this example,balanceX
is DAI, andbalanceY
is USDC. Thus, the spot price will be USDC/DAI. This means the amount of USDC I will get for each DAI.Within Balancer, all stable math calculations within the Balancer's pools are performed in 1e18. With both the primary and secondary balances normalized to 18 decimals, they can be safely passed to the
StableMath._calculateInvariant
andStableMath._calcSpotPrice
functions to compute the spot price. Assuming that the price of USDC and DAI is perfectly symmetric (1 DAI can be exchanged for exactly 1 USDC, and vice versa), the spot price returned from theStableMath._calcSpotPrice
will be1e18
. Note that the spot price returned by theStableMath._calcSpotPrice
function will be denominated in 18 decimals.In Line 47-50 within the
Stable2TokenOracleMath._getSpotPrice
function, it attempts to downscale the spot price to normalize it back to the original decimals and token rate (e.g. stETH back to wstETH) of the token.The
scaleFactor
at Line 47 will be evaluated as follows:Finally, the spot price will be scaled in reverse order and it will be evaluated to
1e6
as shown below:DAI/USDC Spot Price
If it is the opposite where the primary token is USDC (6 decimals) and the secondary token is DAI (18 decimals), the calculation of the spot price will be as follows:
The
scaleFactor
at Line 47 will be evaluated to as follows:Finally, the spot price will be scaled in reverse order and it will be evaluated to
1e30
as shown below:Note about the spot price
Assuming that the spot price of USDC and DAI is 1:1. As shown above, if the decimals of two tokens are not the same, the final spot price will end up either 1e6 (USDC/DAI) or 1e30 (DAI/USDC). However, if the decimals of two tokens (e.g. wstETH and WETH) are the same, this issue stays hidden as the
scaleFactor
in Line 47 will always be 1e18 as bothsecondaryScaleFactor
andprimaryScaleFactor
cancel out each other.It was observed that the spot price returned from the
Stable2TokenOracleMath._getSpotPrice
function is being compared with the oracle price from theTwoTokenPoolUtils._getOraclePairPrice
function to determine if the pool has been manipulated within many functions.Based on the implementation of the
TwoTokenPoolUtils._getOraclePairPrice
function , theoraclePrice
returned by this function is always denominated in 18 decimals regardless of the decimals of the underlying tokens. For instance, assume the spot price of USDC (6 decimals) and DAI (18 decimals) is 1:1. The spot price returned by this oracle function for USDC/DAI will be1e18
and DAI/USDC will be1e18
.In many functions, the spot price returned from the
Stable2TokenOracleMath._getSpotPrice
function is compared with the oracle price via theStable2TokenOracleMath._checkPriceLimit
. Following is one such example. TheoraclePrice
will be1e18
, while thespotPrice
will be either1e6
or1e30
in our example. This will cause the_checkPriceLimit
to always revert because of the large discrepancy between the two prices.https://github.com/sherlock-audit/2022-12-notional/blob/main/contracts/vaults/balancer/internal/math/Stable2TokenOracleMath.sol#L71
Other affected functions include the following:
Impact
A vault supporting tokens with two different decimals will have many of its key functions will be broken as the
_checkPriceLimit
will always revert. For instance, rewards cannot be reinvested and vaults cannot be settled since they rely on the_checkPriceLimit
function.If the reward cannot be reinvested, the strategy tokens held by the users will not appreciate. If the vault cannot be settled, the vault debt cannot be repaid to Notional and the gain cannot be realized. Loss of assets for both users and Notional
Code Snippet
https://github.com/sherlock-audit/2022-12-notional/blob/main/contracts/vaults/balancer/internal/math/Stable2TokenOracleMath.sol#L15
Tool used
Manual Review
Recommendation
Within the
Stable2TokenOracleMath._getSpotPrice
, normalize the spot price back to 1e18 before returning the result. This ensures that it can be compared with the oracle price, which is denominated in 1e18 precision.This has been implemented in the spot price function (
Boosted3TokenPoolUtils._getSpotPriceWithInvariant
) of another pool (Boosted3Token
). However, it was not consistently applied inTwoTokenPool
.The text was updated successfully, but these errors were encountered: