Glamorous Plum Baboon
Medium
The BorrowLogic:executeBorrow
function performs a division operation to calculate the next isolation mode total debt, followed by a conversion to uint128
. This calculation risks precision loss due to integer division and potential silent truncation during the conversion to uint128
. This could lead to inaccuracies in debt tracking and possible over- or under-collateralization in isolation mode.
In the BorrowLogic:executeBorrow
function, the calculation of nextIsolationModeTotalDebt
involves dividing params.amount
by a power of 10 determined by (reserveCache.reserveConfiguration.getDecimals() - ReserveConfiguration.DEBT_CEILING_DECIMALS)
. This result is then cast to a uint128
.
1, Solidity performs integer division, discarding any fractional parts, leading to potential precision loss.
2, The conversion to uint128
risks truncation if the result exceeds the range of uint128
.
This could cause inaccuracies in isolation mode debt calculations and potentially result in unintended protocol behavior.
The calculation:
uint256 nextIsolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
.isolationModeTotalDebt += (params.amount /
10 **
(reserveCache.reserveConfiguration.getDecimals() -
ReserveConfiguration.DEBT_CEILING_DECIMALS)).toUint128();
performs:
- Integer division, leading to truncation of fractional parts.
- Conversion to
uint128
, which can silently truncate values exceeding the maximum range ofuint128
.
i, Precision Loss: Integer division truncates fractional values, leading to imprecise debt calculations.
ii, Truncation on Casting: If the calculated value exceeds uint128
limits, it will be silently truncated.
iii, Risk of Over-/Under-Collateralization: In isolation mode, debt tracking inaccuracies could cause protocol inefficiencies or financial risk to users.
Consider a scenario where:
params.amount = 1,000,000
reserveCache.reserveConfiguration.getDecimals() = 18
ReserveConfiguration.DEBT_CEILING_DECIMALS = 2
The calculation becomes:
nextIsolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
.isolationModeTotalDebt += (1,000,000 / 10 ** (18 - 2)).toUint128();
Here:
- The division results in
1,000,000 / 10 ** 16
, which equals0.0001
but is truncated to0
due to integer division. - The
toUint128
conversion would then cast this incorrect value, resulting in precision loss.
To mitigate the precision loss and truncation:
- Use fixed-point arithmetic libraries, such as
WadRayMath
, to perform precise calculations. - Ensure safe conversion to
uint128
by adding a validation step to confirm the calculated value fits within the range ofuint128
.
uint256 calculatedAmount = (params.amount *
10 ** ReserveConfiguration.DEBT_CEILING_DECIMALS) /
10 ** reserveCache.reserveConfiguration.getDecimals();
require(calculatedAmount <= type(uint128).max, "Overflow in uint128 conversion");
uint256 nextIsolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
.isolationModeTotalDebt += uint128(calculatedAmount);
- Fixed-point arithmetic ensures fractional values are handled correctly.
- Validation check ensures no overflow occurs during the
uint128
conversion.