HACK ANALYSIS 3 min read

Precision Loss in Arithmetic Operations


Precision Loss in Arithmetic Operations

Smart contracts are high-level programs that are translated into EVM byte code and then deployed to the Ethereum blockchain for execution. Solidity mathematic procedures are similar to other programming languages. The following arithmetic operations are applicable to Solidity: Addition, Subtraction, Multiplication, Division (x / y), Modulus (x% y), Exponential (x**y)

Multiply before Division in Solidity:

In the case of performing Integer division, Solidity may truncate the result. Hence we must multiply before dividing to prevent such loss in precision

Let’s take an example,

console.log((50*100*15/15) = 5000

Let’s start with the division. After all, abc/c is equal to a/c * bc.

console.log((50/15)*100*15 = 4999.9999999999995

This is a result of a floating point error. Instead of floating points, we get rounding errors in Solidity, and the second operation in Solidity would produce 4999. We minimize rounding issues as much as possible by performing all multiplications first and division later. The computations can be much more complex, and converting them to a multiplication first formula can sometimes be difficult.

Because of the rounding errors introduced in calculations it has an impact on the accuracy of the values as they will compound when executed multiple times, therefore increasing the overall precision loss.

uint256 numerator = 50 * 100 * 15;
uint256 denominator = 15;

We can always save the pair of numbers and perform our calculations using the correct math. Usually, it won’t matter if a number is rounded down. Just be aware that it might occur and deal with it if necessary.

Multiplication and Division of Contract Value:

Imagine a function that calculates a fee based on the number of coins held.

In this case, as Total_coins will always be greater than the coins held, if we perform division first the result would always result in zero. If we used (coins * fee)/Total_coins then it would have been greater than 1. As in the example shown above, when coins=2 and fees=15, while the total coins are 10, (coins * fee)/Total_coins returns 3.

Hence we must perform multiplication operations before division unless the limit of a smaller type which makes the operation fatal.

Yield V2: Case Scenario:

A real-life exploit can be seen in the code shown below. Yield v2 offered fixed-rate borrowing and lending. The Pool.solcontract in method _update. Line 186 performed division between scaledFYTokenCached and _baseCached before multiplication with timeElapsed. This introduced imprecise accuracy when calculating the cumulativeBalancesRatio.

Best Practices to Avoid Round Off Errors :

The following are the best preventive techniques for imprecise arithmetic orders:

  • Multiplication should always be performed before division to avoid loss of precision.
  • The calculated result for division and multiplication can be stored in an integer with more bits, but the operands must also be integers of the same size.
  • The operands for the exponentiation function must be unsigned integers. Unsigned Integers with lower bits can be calculated and stored as unsigned integers with higher bits.
  • To apply an arithmetic operation to all of the operands, they must all have the same data type; otherwise, the operation will not be performed.

Our product SolidityScan can automatically find potential precision loss errors due to certain arithmetic operations.

Conclusion:

In Solidity, the order of the arithmetic operations is not clearly defined. The operation tree’s children are evaluated in any order, but the order in which they are examined is not specified. Users can carry out various operations on operands using operators. You can rely on SolidityScan to ensure that the proper procedures are followed to achieve top-tier smart contract security. Signup for a free trial at https://solidityscan.com/signup