Nice Laurel Turtle
Medium
GasPriceOracle
contract is designed to handle various gas-related parameters such as the Layer 1 (L1) base fee, overhead costs, and fee scalars, suffers from precision loss in its fee calculation functions getL1FeeBeforeCurie and getL1FeeCurie, due to integer division and limited scalar precision.
The GasPriceOracle
contract suffers from precision loss in its fee calculation functions, specifically in getL1FeeBeforeCurie
and getL1FeeCurie.
This issue arises from the use of integer division and a limited precision scalar (PRECISION), which leads to rounding errors when calculating gas fees.
getL1FeeBeforeCurie
function
function getL1FeeBeforeCurie(bytes memory _data) internal view returns (uint256) {
uint256 _l1GasUsed = getL1GasUsedBeforeCurie(_data);
uint256 _l1Fee = _l1GasUsed * l1BaseFee;
return (_l1Fee * scalar) / PRECISION;
}
It Calculates the L1 fee for transactions before the Curie upgrade. First, it calculates the L1 gas used based on the transaction data. Then it multiplies this by the L1 base fee to get the initial fee. Finally, it applies a scalar and divides by a precision factor to get the final fee.
getL1FeeCurie
function:
function getL1FeeCurie(bytes memory _data) internal view returns (uint256) {
return (commitScalar * l1BaseFee + blobScalar * _data.length * l1BlobBaseFee) / PRECISION;
}
It calculates the L1 fee for transactions after the Curie upgrade. First, it uses a different calculation method, incorporating commit and blob scalars. The cost is based on the L1 base fee, blob base fee, and the length of the transaction data. It also applies a precision factor for the final calculation.
In both functions, the critical issue occurs in the final calculation step, i.e.,
return (...) / PRECISION;
The two functions are designed to provide accurate estimates of gas fees for Layer 1 costs based on different states of the network, but they are susceptible to precision loss due to integer division and limited scalar precision, which can lead to inaccurate fee calculations.
Example:
Here, PRECISION
is typically set to a value like 1e9 (1,000,000,000). This division operation leads to precision loss due to:
- Solidity divides integers by cutting off any decimal parts.
- The PRECISION constant may not provide enough decimal places for highly precise calculations.
Example of the issue:
Let's assume a scenario where:
_l1Fee * scalar
= 1,000,000,001
PRECISION
= 1,000,000,000
The calculation 1,000,000,001 / 1,000,000,000 should ideally result in 1.000000001.
Due to integer division, it results in 1, losing the fractional part. This loss of precision, while small in a single transaction, can accumulate over multiple transactions, leading to significant discrepancies in gas fee calculations. Even minor precision losses can accumulate over multiple transactions, leading to significant financial discrepancies.
- Rounding errors may cause gas fees to be calculated lower than actual costs.
- If fees are underestimated, transactions may not cover the required gas, causing withdrawals and other critical operations to fail.
- Especially withdrawals, slightly lower fees than required can cause transactions to fail due to insufficient gas.
Manual Review
Enhancing the precision of fee calculations by increasing the value of the PRECISION
constant allows for finer granularity in scalar multipliers, reducing the likelihood of significant rounding errors.
uint256 private constant PRECISION = 1e18; // Increased from 1e9 to 1e18