Omniscia Steer Protocol Audit

GaussianPosition Manual Review Findings

GaussianPosition Manual Review Findings

GPN-01M: Inconsistent Rounding of Constants

Description:

The e constant is rounded upwards in relation to its last decimal place (i.e. 2.71...757 rounded to 2.71...76) whereas the π constant is rounded downward (i.e. 3.14...197 rounded to 3.14...19).

Example:

src/GaussianPosition.sol
20uint256 constant E = 271828182845904523536028747135266249776; // e * 1e20
21uint256 constant PI = 314159265358979323846264338327950288419; // pi * 1e20

Recommendation:

We advise rounding to remain consistent in the code as it may result in exacerbated errors in the tail end of the number ranges.

Alleviation:

The E constant has been removed from the codebase rendering this exhibit inapplicable.

GPN-02M: Incorrect Denominator Precision

Description:

The denominator of the GaussianPosition::calculateGaussianPDF function has an accuracy of 1e9 even though its numerator has an accuracy of 1e18.

In detail, the Math.sqrt((2 * PI) / 1e20) calculation will result in a 1e9 accuracy fraction which will not properly normalize the input value.

Impact:

The GaussianPosition::calculateGaussianPDF will operate close to arithmetic boundaries and may fail due to overflows for otherwise valid values.

Example:

src/GaussianPosition.sol
33// Calculate Gaussian PDF with fixed-point arithmetic
34function calculateGaussianPDF(int256 x, uint256 _sigma) public pure returns (uint256) {
35 // Calculate exponent: -(x^2)/(2*sigma^2)
36 uint256 xSquared = uint256(x * x);
37 uint256 sigmaPow2 = _sigma * _sigma; // 1e2 is the precision so for both sigam 1e4
38 uint256 exponent = (xSquared * PRECISION) / (2 * sigmaPow2 / 1e4);
39
40 // Calculate e^(-exponent)
41 uint256 expValue = exponential(exponent);
42 uint256 invertwedExpValue = 1e36 / expValue;
43
44 // Calculate denominator: sigma * sqrt(2π)
45 uint256 denominator = _sigma / 1e2 * sqrt((2 * PI) / 1e20);
46 return (invertwedExpValue) / denominator;
47}

Recommendation:

We advise the input to the square root function to be set to 2 * PI and the result to be divided by 10, ensuring that the final (invertwedExpValue) / denominator calculation eliminates the 1e18 padding added to the base x input value's denomination.

Alleviation:

The Steer Protocol team evaluated this exhibit and stated that they do not anticipate any issues in the precisions of the denominator as the scaling values across the system are handled correctly.

Given that the Steer Protocol team does not anticipate to configure the contract in such a way where mathematical errors would manifest due to the outlined methodology, we consider this exhibit safely acknowledged.

GPN-03M: Non-Standard Addition of Extra Tick Range in Even Positions

Description:

The GaussianPosition::calculatePosition function will add an extra tick range anticipating an upward price movement when an even number of positions is requested.

Impact:

The system will inefficiently deploy liquidity when an AMM pair's price moves downward and the GaussianPosition has been configured in an even-position distribution.

Example:

src/GaussianPosition.sol
95if (_numPositions % 2 == 0) {
96 // Even number of positions
97 startOffset = -int24(_numPositions / 2 - 1);
98 endOffset = int24(_numPositions / 2);
99} else {
100 // Odd number of positions
101 startOffset = -int24(_numPositions / 2);
102 endOffset = int24(_numPositions / 2);
103}

Recommendation:

We advise the code to instead branch its logic to add an additional position either to the end or the start of the overall ranges covered by evaluating whether the currentTick is closer to the next tick space or the previous one.

Alleviation:

The code was updated per our recommendation, optimizing the in-range liquidity deployment of the system.

GPN-04M: Denominator Precision Loss of Sigma in Square Root Calculation

Description:

The GaussianPosition::calculateGaussianPDF function will incorrectly round the σ value downward prior to multiplying it with the square root of π times 2 in contradiction with the Gaussian PDF formulae.

Impact:

A configuration of σ at a value less-than 1e2 is permitted even though it would lead to consistent division-by-zero errors in the GaussianPosition::calculateGaussianPDF function.

Example:

src/GaussianPosition.sol
26constructor(uint24 _numPositions, uint256 _sigma) {
27 require(_numPositions > 0, "POS");
28 require(_sigma > 0, "SIG");
29 numPositions = _numPositions;
30 sigma = _sigma;
31}
32
33// Calculate Gaussian PDF with fixed-point arithmetic
34function calculateGaussianPDF(int256 x, uint256 _sigma) public pure returns (uint256) {
35 // Calculate exponent: -(x^2)/(2*sigma^2)
36 uint256 xSquared = uint256(x * x);
37 uint256 sigmaPow2 = _sigma * _sigma; // 1e2 is the precision so for both sigam 1e4
38 uint256 exponent = (xSquared * PRECISION) / (2 * sigmaPow2 / 1e4);
39
40 // Calculate e^(-exponent)
41 uint256 expValue = exponential(exponent);
42 uint256 invertwedExpValue = 1e36 / expValue;
43
44 // Calculate denominator: sigma * sqrt(2π)
45 uint256 denominator = _sigma / 1e2 * sqrt((2 * PI) / 1e20);
46 return (invertwedExpValue) / denominator;
47}

Recommendation:

We advise the code to multiply and then divide, ensuring all potential σ values are permitted.

Alleviation:

The order of operations in the referenced statement were re-ordered per our recommendation, maximizing the potential accuracy of the σ value.

GPN-05M: Inexistent Assignment of Relative Weight

Description:

The relativeWeight value of a positions entry is never assigned to a non-zero value, resulting in misbehaviours within the PluginHelper::calcTokenAmounts function.

Impact:

The absence of correct relativeWeight configurations is considered an error in the PluginHelper that can result in transaction reverts.

Example:

src/GaussianPosition.sol
105uint24 posIndex = 0;
106// Calculate weights and positions
107for (int24 i = startOffset; i <= endOffset; i++) {
108 // Calculate Gaussian weight
109 uint256 weight = calculateGaussianPDF(i, _sigma);
110 totalWeight += weight;
111 // Calculate lower and upper ticks
112 int24 lowerTick = centerTick + i * tickSpacing;
113 int24 upperTick = lowerTick + tickSpacing;
114
115 // Store position
116 positions[posIndex] = LiquidityPositions({
117 lowerTick: lowerTick,
118 upperTick: upperTick,
119 relativeWeight: 0 // will be added in the next loop
120 });
121 weights[posIndex] = weight;
122 posIndex++;
123}
124
125uint256[] memory positionT0Requested;
126uint256[] memory positionT1Requested;
127uint256 totalT0Requested;
128uint256 totalT1Requested;
129positionT0Requested = new uint256[](_numPositions);
130positionT1Requested = new uint256[](_numPositions);
131// Step 2: Calculate liquidity and normalize weights
132for (uint256 i = 0; i < uint24(_numPositions); i++) {
133 // Normalize the weight by dividing by totalWeight
134 uint256 normalizedWeight = weights[i] * 1e4 / totalWeight;

Recommendation:

We advise the relativeWeight to be assigned to the weight calculated and the weights data entry as well as posIndex variable to be omitted entirely.

In the ensuing loop, the relativeWeight should be re-assigned as positions[i].relativeWeight * 1e4 / totalWeight and utilized for the normalizedWeight calculation, optimizing the code's gas cost and addressing the issue outlined.

Alleviation:

The code was updated to calculate the relativeWeight correctly whilst retaining the weights data entries in place due to their increased precision.

As the increased precision is beneficial to the system and the original issue has been addressed, we consider this exhibit properly alleviated.

GPN-06M: Inexistent Boundaries of Ticks

Description:

The lowerTick and upperTick calculations in the GaussianPosition::calculatePosition function will not cap the tick values to the maximum and minimum permitted values in a particular pair, resulting in the calculation of unfulfillable positions and thus transaction reverts.

Impact:

The GaussianPosition contract will be unusable close to the tick boundaries of a particular AMM.

Example:

src/GaussianPosition.sol
85// Initialize positions array
86positions = new LiquidityPositions[]((_numPositions));
87liquidity = new uint128[]((_numPositions));
88uint256[] memory weights = new uint256[]((_numPositions));
89uint256 totalWeight;
90int24 centerTick = (currentTick / tickSpacing) * tickSpacing;
91// Calculate start and end indices
92int24 startOffset;
93int24 endOffset;
94
95if (_numPositions % 2 == 0) {
96 // Even number of positions
97 startOffset = -int24(_numPositions / 2 - 1);
98 endOffset = int24(_numPositions / 2);
99} else {
100 // Odd number of positions
101 startOffset = -int24(_numPositions / 2);
102 endOffset = int24(_numPositions / 2);
103}
104
105uint24 posIndex = 0;
106// Calculate weights and positions
107for (int24 i = startOffset; i <= endOffset; i++) {
108 // Calculate Gaussian weight
109 uint256 weight = calculateGaussianPDF(i, _sigma);
110 totalWeight += weight;
111 // Calculate lower and upper ticks
112 int24 lowerTick = centerTick + i * tickSpacing;
113 int24 upperTick = lowerTick + tickSpacing;
114
115 // Store position
116 positions[posIndex] = LiquidityPositions({
117 lowerTick: lowerTick,
118 upperTick: upperTick,
119 relativeWeight: 0 // will be added in the next loop
120 });
121 weights[posIndex] = weight;
122 posIndex++;
123}

Recommendation:

We advise the loop to halt early, creating fewer positions than desired if any of the calculated positions would flow out of bounds.

Alleviation:

The code was updated to skip a particular position if it exceeds sane bounds, alleviating this exhibit.

GPN-07M: Loss of Denominator Precision in Exponent

Description:

The GaussianPosition::calculateGaussianPDF function is meant to calculate the Gaussian probability density function with a mean configuration of 0:

f(x) = \frac{e^{-\frac{x^2}{2\sigma^2}}}{\sqrt{2\pi\sigma^2}}

In the current implementation, an error exists in the σ calculation and specifically its low precision and early normalization. The denominator is calculated as 2 * σ * σ / 1e4 where σ is a 1e4 accuracy fractional number.

The normalization by 1e4 will result in the calculation rounding to 0 or a significantly lower value than expected for low σ configurations unnecessarily.

Impact:

Lower σ value configurations for the Gaussian PDF will result in transaction reverts and lower evaluations than expected due to early normalization of the σ value's fractional nature.

Example:

src/GaussianPosition.sol
35// Calculate exponent: -(x^2)/(2*sigma^2)
36uint256 xSquared = uint256(x * x);
37uint256 sigmaPow2 = _sigma * _sigma; // 1e2 is the precision so for both sigam 1e4
38uint256 exponent = (xSquared * PRECISION) / (2 * sigmaPow2 / 1e4);

Recommendation:

We advise the 1e4 division from the denominator to be converted to a multiplicand for the numerator, maximizing the exponent's accuracy and greatly expanding the range of supported σ configurations.

Alleviation:

The adjustment we have recommended in the formula has been applied, greatly increasing the usable range of σ configurations.

GPN-08M: Improper Rounding of Nearest Tick

Description:

The GaussianPosition::calculatePosition function will improperly round negative ticks toward 0, resulting in liquidity deployment that can be entirely out-of-range.

Impact:

While the same exhibit is present and explained in the FlatPosition3 contract, in this instance it is of higher severity as a _numPositions configuration of 1 would result in liquidity being deployed out-of-range incorrectly.

Example:

src/GaussianPosition.sol
90int24 centerTick = (currentTick / tickSpacing) * tickSpacing;

Recommendation:

We advise the nearest tick multiple to be calculated whilst rounding toward negative infinity, ensuring that the liquidity deployment performed is optimal.

Alleviation:

The code was updated to properly round toward the desirable direction, and was additionally enhanced to support a fixed center tick for liquidity deployments.

We consider the refactored version of the code to properly deploy liquidity in-range and thus this exhibit to be alleviated.