Status DataClose notification

Kinetic Disclosed Report

Audit Report

Price Validation and Arithmetic Vulnerabilities in RedeemBurnRateCalculatorV3

Target

https://github.com/kinetic-market/public-money-market-contracts

Vulnerability details

The shouldSkipBurnRate function in RedeemBurnRateCalculatorV3.sol contains several vulnerabilities related to price validation and arithmetic operations that could lead to incorrect calculations or system manipulation.

The issues could result in:

  • Incorrect burn rate calculations due to zero prices
  • Precision loss in token value calculations
  • Potential overflow in large balance/price scenarios
  • System manipulation through price oracle exploitation

Validation steps

Vulnerable function:

// file contracts/Tokenomics/RedeemBurnRateCalculatorV3.sol
 function shouldSkipBurnRate(
        address user,
        uint256 /*amount*/
    ) external view override returns (bool) {
        if (_exclusionListedAddresses.contains(user))
            return false;

        if (useOraclePrice) {
            uint256 price = priceOracle.getPrice(address(protocolToken));

            return ((price * esProtocolToken.balanceOf(user) / (10**esProtocolTokenDecimals)) > burnRateThresholdUSD);
        }
        uint amountOut = getAmountOut();

        uint esProtocolWNativePrice = amountOut * esProtocolToken.balanceOf(user) / (10**esProtocolTokenDecimals);

        uint sNativePrice =  priceOracle.getPrice(address(sETH));

        esProtocolWNativePrice = esProtocolWNativePrice * sNativePrice / (10**sETHDecimals);

        return esProtocolWNativePrice > burnRateThresholdUSD;
    }
  1. If oracle returns price = 0, calculations will return 0, leading to incorrect burn rate decisions
  2. Large token balances combined with prices could cause overflow
  3. Multiple divisions after multiplications can lead to significant precision loss

Recommended fix:

function shouldSkipBurnRate(address user, uint256 /*amount*/) external view returns (bool) {
    require(user != address(0), "Zero address");
    
    if (_exclusionListedAddresses.contains(user))
        return false;

    uint256 userBalance = esProtocolToken.balanceOf(user);
    if (userBalance == 0) return false;

    if (useOraclePrice) {
        uint256 price = priceOracle.getPrice(address(protocolToken));
        require(price > 0, "Invalid price");
        
        // Use SafeMath or checked arithmetic
        uint256 userValue = (price * userBalance) / (10**esProtocolTokenDecimals);
        return userValue > burnRateThresholdUSD;
    }

    uint256 amountOut = getAmountOut();
    require(amountOut > 0, "Invalid amount out");
    
    uint256 sNativePrice = priceOracle.getPrice(address(sETH));
    require(sNativePrice > 0, "Invalid sETH price");

    uint256 userValue = ((amountOut * userBalance) / (10**esProtocolTokenDecimals)) * 
                       sNativePrice / (10**sETHDecimals);
    
    return userValue > burnRateThresholdUSD;
}

Comments are hidden

This information is not publicly available.