Assessment reports>Programmable Derivatives>Medium findings>Precision loss in the ,getCreateAmount, and ,getRedeemAmount, functions
Category: Business Logic

Precision loss in the getCreateAmount and getRedeemAmount functions

Medium Severity
Medium Impact
Medium Likelihood

Description

The value of ethPrice includes oracleDecimals decimal places. To calculate the create amount, it needs to be converted to a base unit by division. This conversion is done when calculating the tvl, which may lead to a loss of precision in subsequent calculations. One possibility is that the calculation result of creationRate is zero, resulting in a division-by-zero error when calculating the create amount.

function getCreateAmount(
  // [...]
  ) public pure returns(uint256) {
  // [...]

! uint256 tvl = (ethPrice * poolReserves).toBaseUnit(oracleDecimals);
  // [...]
  if (collateralLevel <= COLLATERAL_THRESHOLD) {
    creationRate = (tvl * multiplier) / assetSupply;
  } else if (tokenType == TokenType.LEVERAGE) {
    // [...]

    uint256 adjustedValue = tvl - (BOND_TARGET_PRICE * bondSupply);
    creationRate = (adjustedValue * PRECISION) / assetSupply;
  }
  
  return ((depositAmount * ethPrice * PRECISION) / creationRate).toBaseUnit(oracleDecimals);
}

The function getRedeemAmount has similar precision-loss issues as the function getCreateAmount when calculating the redeem amount, manifested as it potentially returning zero. One is because converting ethPrice to base units is completed when calculating the tvl, and the other is because PRECISION is multiplied after division when calculating the redeemRate.

function getRedeemAmount(
  // [...]
) public pure returns(uint256) {
  // [...]

! uint256 tvl = (ethPrice * poolReserves).toBaseUnit(oracleDecimals);
  // [...]
  
  // Calculate the redeem rate based on the collateral level and token type
  uint256 redeemRate;
  if (collateralLevel <= COLLATERAL_THRESHOLD) {
    redeemRate = ((tvl * multiplier) / assetSupply);
  } else if (tokenType == TokenType.LEVERAGE) {
!   redeemRate = ((tvl - (bondSupply * BOND_TARGET_PRICE)) / assetSupply) * PRECISION;
  } else {
    redeemRate = BOND_TARGET_PRICE * PRECISION;
  }
  
  // Calculate and return the final redeem amount
  return ((depositAmount * redeemRate).fromBaseUnit(oracleDecimals) / ethPrice) / PRECISION;
}

Impact

The creation transaction may fail due to division by zero, and the redemption transaction may fail due to the redeem amount being zero.

function _redeem(
  // [...]
  ) private returns(uint256) {
  // [...]
  uint256 reserveAmount = simulateRedeem(tokenType, depositAmount);

  // [...]

  // Reserve amount should be higher than zero
  if (reserveAmount == 0) {
    revert ZeroAmount();
  }

  // [...]
}

Recommendations

Multiplication should always be performed before division to avoid loss of precision. Also, consider performing the conversion of ethPrice during the calculation of the create amount and redeem amount.

Remediation

Plaza Finance provided following message:

It’s a marginal amount on fairly unrealistic market conditions for the Pools we will release.

Zellic © 2025Back to top ↑