Assessment reports>Programmable Derivatives>High findings>Decimals of the data in the function ,latestRoundData
Category: Business Logic

Decimals of the data in the function latestRoundData

High Severity
High Impact
Medium Likelihood

Description

The number of decimals in different price feeds may vary. The function getOraclePrice can retrieve the latest price from a price feed, and the function getOracleDecimals can retrieve the number of decimals used in the corresponding price feed.

Because Balancer math works with price data with 18 decimals, the decimals of the price data need to be converted from the corresponding price-feed decimals to 18 decimals. But in the current implementation, the price data undergoes decimals conversion based on the BalancerOracleAdapter's state variable decimals. This may result in incorrect prices being used in the calculation of the pool's fair price and could potentially lead to prices becoming zero due to precision loss.

function latestRoundData()
    external
    view
    returns (uint80, int256, uint256, uint256, uint80){
    // [...]
    for(uint8 i = 0; i < tokens.length; i++) {
!     prices[i] = getOraclePrice(address(tokens[i]), ETH).toBaseUnit(decimals); // balancer math works with 18 dec
    }
    // [...]
}

function toBaseUnit(uint256 amount, uint8 decimals) internal pure returns (uint256) {
    return amount / (10 ** decimals);
}

Since BalancerOracleAdapter inherits from AggregatorV3Interface, the decimals in the return price of the function latestRoundData should be consistent with the state variable decimals. Thus, the decimals of fairUintUSDPrice need to be converted to align with the state variable decimals.

function latestRoundData()
    external
    view
    returns (uint80, int256, uint256, uint256, uint80){
    // [...]

    uint256 fairUintETHPrice = _calculateFairUintPrice(prices, weights, pool.getInvariant(), pool.getActualSupply());
!   uint256 fairUintUSDPrice = fairUintETHPrice.mulDown(getOraclePrice(ETH, USD));

    if (fairUintUSDPrice > uint256(type(int256).max)) {
      revert PriceTooLargeForIntConversion();
    }

    return (uint80(0), int256(fairUintUSDPrice), block.timestamp, block.timestamp, uint80(0));
}

Impact

The function latestRoundData may return an incorrect price.

Recommendations

Consider converting the price decimals involved in the calculation of the pool's fair price from the corresponding price-feed decimals to 18 decimals.

Consider converting the decimals of fairUintUSDPrice to match the decimals defined in the BalancerOracleAdapter.

Remediation

This issue has been acknowledged by Plaza Finance, and fixes were implemented in the following commits:

Zellic © 2025Back to top ↑