Precision loss in the getCreateAmount
and getRedeemAmount
functions
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.