DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: medium
Invalid

1e6 is not enough precision in oracle price for some tokens

Summary

There is another problem in LibUsdOracle.getUsdPrice() regarding decimals described in submission with id clxu0ki7h000bmco763z783ra. So correct code is to return how many Token can be bought for 1 USD. For example for USDC LibUsdOracle.getUsdPrice() will return 1e6.

Problem is that such precision where 1e6 means 1 USD is not enough for some tokens.

Another note is that any Well can be whitelisted via governance:
https://docs.bean.money/almanac/farm/sun#minting-whitelist

Vulnerability Details

As described in another report, calculation must be refactored to handle token decimals because it is expected in consumer libraries:

function getUsdPrice(address token, uint256 lookback) internal view returns (uint256) {
if (token == C.WETH) {
uint256 ethUsdPrice = LibEthUsdOracle.getEthUsdPrice(lookback);
if (ethUsdPrice == 0) return 0;
return uint256(1e24).div(ethUsdPrice);
}
if (token == C.WSTETH) {
uint256 wstethUsdPrice = LibWstethUsdOracle.getWstethUsdPrice(lookback);
if (wstethUsdPrice == 0) return 0;
return uint256(1e24).div(wstethUsdPrice);
}
// 1e18 * 1e6 = 1e24.
uint256 tokenPrice = getTokenPriceFromExternal(token, lookback);
if (tokenPrice == 0) return 0;
- return uint256(1e24).div(tokenPrice);
+ return uint256(10 ** ERC20(token).decimals() * 1e6).div(tokenPrice);
}

Main calculation is performed in LibDeltaB.calculateDeltaBFromReserves(). ratios is value returned from that getUsdPrice()

function calculateDeltaBFromReserves(
address well,
uint256[] memory reserves,
uint256 lookback
) internal view returns (int256) {
...
@> (uint256[] memory ratios, uint256 beanIndex, bool success) = LibWell.getRatiosAndBeanIndex(
tokens,
lookback
);
...
return
int256(
@> IBeanstalkWellFunction(wellFunction.target).calcReserveAtRatioSwap(
reserves,
beanIndex,
ratios,
wellFunction.data
)
).sub(int256(reserves[beanIndex]));
}

And finally calcReserveAtRatioSwap() calculates ideal Bean reserve according to this formula:
https://github.com/BeanstalkFarms/Basin/blob/master/src/functions/ConstantProduct2.sol#L84-L93

reserve = (reserves[i] * reserves[j]).mulDiv(ratios[j], ratios[i]).sqrt();

Let's observe error in calculation by example of Bean/wBTC Well. Suppose Bean reserve is 100000e6, wBTC reserve is 1e8, wBTC price is $100k.

  1. Ratio of wBTC according to corrected formula is 1e8 * 1e6 / 100000e6 = 1e3. Ratio of Bean is 1e6.

  2. Because of rounding error in calculation of wBTC, possible ratio is +- 1, so abs difference possible is 2.

  3. Let's calculate the biggest possible reserve when price is maximal possible according to step 2: reserve = sqrt(1e8 * 100000e6 * 1e6 / (1e3 + 1) = 0.9995e11

  4. Let's calculate the lowest possible reserve when price is minimal possible according to step 2: reserve = sqrt(1e8 * 100000e6 * 1e6 / (1e3 - 1) = 1.0005e11

  5. Difference is 1 - 1.0005e11 / 0.9995e11 = 0.001 = 0.1%

As described, 0.1% difference is possible solely because of 1e6 precision used because it's insufficient for low-decimal high-value token such as wBTC.

Impact

Error in calculation becomes significant for low-decimal high-value tokens. For Bean/wBTC Well where wBTC price is $100k error in calculation is 0.05% and increases with increase of wBTC price.

It results in wrong calculations of deltaB reserves, i.e. in core peg mechanism of Bean. In other words such error in calculations affects Bean peg to 1 USD, where 0.05% is significant difference.

Tools Used

Manual Review

Recommendations

Refactor price calculations to use higher precision.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.