The AntiMomentumUpdateRule contract experiences precision loss during weight normalization calculations, leading to weight deviations of approximately 1.12% and affecting the invariant that weights must sum to exactly 100%. While significant, the gradual nature of these deviations and existing system mitigations reduce the immediate economic impact.
The issue occurs in the normalization calculation where division operations cause precision loss that propagates through the weight calculations:
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import "@prb/math/contracts/PRBMathSD59x18.sol";
import "../../../contracts/mock/mockRules/MockAntiMomentumRule.sol";
import "../../../contracts/mock/MockPool.sol";
import "../utils.t.sol";
contract QuantammAntiMomentumTest is Test, QuantAMMTestUtils {
MockAntiMomentumRule public rule;
MockPool public mockPool;
function setUp() public {
rule = new MockAntiMomentumRule(address(this));
mockPool = new MockPool(3600, 1 ether, address(rule));
}
function assertRunCompletes(
uint256 numAssets,
int256[][] memory parameters,
int256[] memory previousAlphas,
int256[] memory prevMovingAverages,
int256[] memory movingAverages,
int128[] memory lambdas,
int256[] memory prevWeights,
int256[] memory data
) internal {
mockPool.setNumberOfAssets(numAssets);
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
previousAlphas,
numAssets
);
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambdas,
movingAverages
);
}
function testPrecisionLossInNormalization() public {
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = 7.1e18;
parameters[0][1] = 2.9e18;
int256[] memory previousAlphas = new int256[]();
previousAlphas[0] = PRBMathSD59x18.fromInt(1);
previousAlphas[1] = PRBMathSD59x18.fromInt(1);
int256[] memory prevMovingAverages = new int256[]();
prevMovingAverages[0] = 1e18;
prevMovingAverages[1] = 1e18;
int256[] memory movingAverages = new int256[]();
movingAverages[0] = 1.1e18;
movingAverages[1] = 0.9e18;
int128[] memory lambdas = new int128[]();
lambdas[0] = int128(0.7e18);
lambdas[1] = int128(0.7e18);
int256[] memory prevWeights = new int256[]();
prevWeights[0] = 0.5e18;
prevWeights[1] = 0.5e18;
int256[] memory data = new int256[]();
data[0] = 1.1e18;
data[1] = 0.9e18;
int256[] memory expectedResults = new int256[]();
expectedResults[0] = 0.5e18;
expectedResults[1] = 0.5e18;
assertRunCompletes(
2,
parameters,
previousAlphas,
prevMovingAverages,
movingAverages,
lambdas,
prevWeights,
data
);
int256[] memory actualResults = rule.GetResultWeights();
int256 weightSum = actualResults[0] + actualResults[1];
assertEq(weightSum, 1e18, "Weight sum should be exactly 100%");
emit log_named_int("Expected weight 0", 0.5e18);
emit log_named_int("Actual weight 0", actualResults[0]);
emit log_named_int("Precision loss", actualResults[0] - 0.5e18);
assertTrue(actualResults[0] != 0.5e18, "Should show precision loss");
assertTrue(actualResults[1] != 0.5e18, "Should show precision loss");
assertEq(actualResults[0] + actualResults[1], 1e18, "Weights must sum to 100%");
}
function testPrecisionLossOverMultipleUpdates() public {
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = 7.1e18;
parameters[0][1] = 2.9e18;
int256[] memory previousAlphas = new int256[]();
previousAlphas[0] = PRBMathSD59x18.fromInt(1);
previousAlphas[1] = PRBMathSD59x18.fromInt(1);
int256[] memory prevMovingAverages = new int256[]();
int256[] memory movingAverages = new int256[]();
int128[] memory lambdas = new int128[]();
lambdas[0] = int128(0.7e18);
lambdas[1] = int128(0.7e18);
int256[] memory prevWeights = new int256[]();
prevWeights[0] = 0.5e18;
prevWeights[1] = 0.5e18;
int256[] memory data = new int256[]();
for (uint i = 0; i < 5; i++) {
prevMovingAverages[0] = 1e18 + int256(i) * 0.1e18;
prevMovingAverages[1] = 1e18 - int256(i) * 0.1e18;
movingAverages[0] = prevMovingAverages[0] + 0.05e18;
movingAverages[1] = prevMovingAverages[1] - 0.05e18;
data[0] = movingAverages[0];
data[1] = movingAverages[1];
assertRunCompletes(
2,
parameters,
previousAlphas,
prevMovingAverages,
movingAverages,
lambdas,
prevWeights,
data
);
int256[] memory results = rule.GetResultWeights();
prevWeights = results;
emit log_named_uint("Update iteration", i + 1);
emit log_named_int("Weight 0", results[0]);
emit log_named_int("Weight 1", results[1]);
emit log_named_int("Cumulative precision loss 0", results[0] - 0.5e18);
emit log_named_int("Cumulative precision loss 1", results[1] - 0.5e18);
assertEq(results[0] + results[1], 1e18, "Weight sum should remain 100%");
}
}
}