Precision Errors Remediation
Overview
Precision errors in Solidity arise from integer division truncating fractional bits before a subsequent multiplication, or from insufficient scaling factors in financial calculations. The remediation requires reordering operations to multiply before dividing, using 1e18-scaled fixed-point arithmetic, and applying explicit rounding direction in vault implementations.
Related Detector: Precision Errors
Recommended Fix
Before (Vulnerable)
// Division before multiplication — systematic precision loss
function calculateFee(uint256 amount) public pure returns (uint256) {
// For amount = 999: (999 / 1000) = 0, then 0 * 3 = 0 (fee evaded)
return (amount / 1000) * FEE_RATE;
}
After (Fixed)
// Multiplication before division — correct precision
function calculateFee(uint256 amount) public pure returns (uint256) {
// For amount = 999: 999 * 3 = 2997, then 2997 / 1000 = 2 (correct)
return (amount * FEE_RATE) / FEE_BASIS;
}
Alternative Mitigations
OpenZeppelin’s Math.mulDiv with explicit rounding for ERC-4626 vaults:
import "@audited/utils/math/Math.sol";
contract SafeVault {
uint256 public totalShares;
uint256 public totalAssets;
// Round DOWN for deposits (user gets fewer shares — protocol-favorable)
function convertToShares(uint256 assets) public view returns (uint256) {
return Math.mulDiv(assets, totalShares + 1, totalAssets + 1, Math.Rounding.Floor);
}
// Round DOWN for withdrawals (user gets fewer assets — protocol-favorable)
function convertToAssets(uint256 shares) public view returns (uint256) {
return Math.mulDiv(shares, totalAssets + 1, totalShares + 1, Math.Rounding.Floor);
}
}
The +1 virtual offset on both totalShares and totalAssets defeats the inflation attack: even when the vault is empty, the ratio is 1:1, not 0:0.
Fixed-point libraries for complex financial math:
// PRBMath library for 18-decimal fixed-point arithmetic
import { UD60x18, ud } from "@prb/math/src/UD60x18.sol";
function calculateInterest(UD60x18 principal, UD60x18 rate, UD60x18 time) public pure returns (UD60x18) {
return principal.mul(rate).mul(time); // All operations maintain 18-decimal precision
}
Common Mistakes
Using SafeMath with division before multiplication — SafeMath prevents overflow but does not fix operation ordering. Division before multiplication loses precision even with SafeMath.
Assuming uniform 18 decimals — USDC uses 6 decimals, not 18. Mixing 6-decimal and 18-decimal tokens without explicit scaling produces systematic errors.
Symmetric rounding direction — vaults that round the same direction for both deposits and withdrawals can be drained through repeated deposit/withdraw cycles.