The ChannelFollowingUpdateRule contract exhibits a compounding deviation from the fundamental invariant that pool weights must sum exactly to 1e18 (100%). Under extreme conditions, the deviation grows linearly at +10 wei per update, leading to significant cumulative errors over time. Even under normal conditions, the rule shows consistent negative deviations. This compounding behavior makes this a more severe issue than similar weight deviations in other rules.
The issue occurs in the weight calculation logic where there's no validation that the sum of weights equals 1e18, and more critically, where deviations compound over time:
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "@prb/math/contracts/PRBMathSD59x18.sol";
import "../../../contracts/mock/mockRules/MockChannelFollowing.sol";
import "../../../contracts/mock/MockPool.sol";
import "../utils.t.sol";
contract QuantammChannelFollowingTotalWeightTest is Test, QuantAMMTestUtils {
MockChannelFollowingRule public rule;
MockPool public mockPool;
int256[][] public parameters;
int256[] public prevWeights;
int256[] public data;
int256[] public variances;
int256[] public prevMovingAverages;
int128[] public lambda;
function setUp() public {
rule = new MockChannelFollowingRule(address(this));
mockPool = new MockPool(3600, 1 ether, address(rule));
setupParameters();
setupInitialState();
}
function setupParameters() private {
parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = 0.1e18;
parameters[1] = new int256[]();
parameters[1][0] = 0.5e18;
parameters[2] = new int256[]();
parameters[2][0] = 0.2e18;
parameters[3] = new int256[]();
parameters[3][0] = 1e18;
parameters[4] = new int256[]();
parameters[4][0] = 0.541519e18;
parameters[5] = new int256[]();
parameters[5][0] = 1e18;
parameters[6] = new int256[]();
parameters[6][0] = 0;
}
function setupInitialState() private {
prevWeights = new int256[](2);
prevWeights[0] = 0.5e18;
prevWeights[1] = 0.5e18;
data = new int256[](2);
data[0] = 1.01e18;
data[1] = 0.99e18;
variances = new int256[](2);
variances[0] = 1e18;
variances[1] = 1e18;
prevMovingAverages = new int256[](4);
prevMovingAverages[0] = 1e18;
prevMovingAverages[1] = 1e18;
prevMovingAverages[2] = 1e18;
prevMovingAverages[3] = 1e18;
lambda = new int128[](1);
lambda[0] = int128(0.95e18);
}
function abs(int256 x) internal pure returns (int256) {
return x >= 0 ? x : -x;
}
function testChannelFollowingTotalWeightNormal() public {
setupParameters();
setupInitialState();
runTotalWeightTest("Normal Conditions");
}
function testChannelFollowingTotalWeightExtreme() public {
setupParameters();
setupInitialState();
parameters[0][0] = 10e18;
parameters[1][0] = 0.1e18;
parameters[2][0] = 5e18;
prevWeights[0] = 0.99e18;
prevWeights[1] = 0.01e18;
data[0] = 20e18;
data[1] = 0.05e18;
runTotalWeightTest("Extreme Conditions");
}
function runTotalWeightTest(string memory testType) private {
mockPool.setNumberOfAssets(2);
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
variances,
2
);
int256[] memory results;
int256 maxPositiveDeviation;
int256 maxNegativeDeviation;
emit log_string("=== Test Type: ");
emit log_string(testType);
emit log_string(" ===");
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambda,
prevMovingAverages
);
results = rule.GetResultWeights();
prevWeights = results;
for (uint i = 0; i < 24; i++) {
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambda,
prevMovingAverages
);
results = rule.GetResultWeights();
int256 totalWeight = results[0] + results[1];
int256 deviation = totalWeight - 1e18;
emit log_named_uint("Update", i + 1);
emit log_named_int("Total weight", totalWeight);
emit log_named_int("Deviation", deviation);
if (deviation > maxPositiveDeviation) {
maxPositiveDeviation = deviation;
}
if (deviation < maxNegativeDeviation) {
maxNegativeDeviation = deviation;
}
prevWeights = results;
}
emit log_string("=== Final Results ===");
emit log_named_int("Max positive deviation", maxPositiveDeviation);
emit log_named_int("Max negative deviation", maxNegativeDeviation);
if (keccak256(bytes(testType)) == keccak256(bytes("Normal Conditions"))) {
assertLt(
abs(maxNegativeDeviation),
3,
"Normal conditions should have minimal negative deviation"
);
assertEq(
maxPositiveDeviation,
0,
"Normal conditions should not have positive deviation"
);
} else {
assertTrue(
maxPositiveDeviation > 0,
"Extreme conditions should show positive deviation"
);
}
}
}
=== Test Type:
Normal Conditions
===
Update: 1
Total weight: 1000000000000000000
Deviation: 0
Update: 2
Total weight: 1000000000000000000
Deviation: 0
Update: 3
Total weight: 999999999999999999
Deviation: -1
Update: 4
Total weight: 999999999999999999
Deviation: -1
Update: 5
Total weight: 999999999999999999
Deviation: -1
Update: 6
Total weight: 999999999999999999
Deviation: -1
Update: 7
Total weight: 999999999999999999
Deviation: -1
Update: 8
Total weight: 999999999999999999
Deviation: -1
Update: 9
Total weight: 999999999999999999
Deviation: -1
Update: 10
Total weight: 999999999999999999
Deviation: -1
Update: 11
Total weight: 999999999999999999
Deviation: -1
Update: 12
Total weight: 999999999999999999
Deviation: -1
Update: 13
Total weight: 999999999999999999
Deviation: -1
Update: 14
Total weight: 999999999999999999
Deviation: -1
Update: 15
Total weight: 999999999999999999
Deviation: -1
Update: 16
Total weight: 999999999999999999
Deviation: -1
Update: 17
Total weight: 999999999999999999
Deviation: -1
Update: 18
Total weight: 999999999999999999
Deviation: -1
Update: 19
Total weight: 999999999999999999
Deviation: -1
Update: 20
Total weight: 999999999999999999
Deviation: -1
Update: 21
Total weight: 999999999999999999
Deviation: -1
Update: 22
Total weight: 999999999999999999
Deviation: -1
Update: 23
Total weight: 999999999999999998
Deviation: -2
Update: 24
Total weight: 999999999999999998
Deviation: -2
=== Final Results ===
Max positive deviation: 0
Max negative deviation: -2
=== Test Type:
Extreme Conditions
===
Update: 1
Total weight: 999999999999999980
Deviation: -20
Update: 2
Total weight: 999999999999999980
Deviation: -20
Update: 3
Total weight: 999999999999999970
Deviation: -30
Update: 4
Total weight: 999999999999999980
Deviation: -20
Update: 5
Total weight: 999999999999999990
Deviation: -10
Update: 6
Total weight: 1000000000000000000
Deviation: 0
Update: 7
Total weight: 1000000000000000000
Deviation: 0
Update: 8
Total weight: 1000000000000000010
Deviation: 10
Update: 9
Total weight: 1000000000000000010
Deviation: 10
Update: 10
Total weight: 1000000000000000010
Deviation: 10
Update: 11
Total weight: 1000000000000000010
Deviation: 10
Update: 12
Total weight: 1000000000000000010
Deviation: 10
Update: 13
Total weight: 1000000000000000010
Deviation: 10
Update: 14
Total weight: 1000000000000000020
Deviation: 20
Update: 15
Total weight: 1000000000000000030
Deviation: 30
Update: 16
Total weight: 1000000000000000040
Deviation: 40
Update: 17
Total weight: 1000000000000000050
Deviation: 50
Update: 18
Total weight: 1000000000000000050
Deviation: 50
Update: 19
Total weight: 1000000000000000060
Deviation: 60
Update: 20
Total weight: 1000000000000000060
Deviation: 60
Update: 21
Total weight: 1000000000000000070
Deviation: 70
Update: 22
Total weight: 1000000000000000080
Deviation: 80
Update: 23
Total weight: 1000000000000000090
Deviation: 90
Update: 24
Total weight: 1000000000000000100
Deviation: 100
=== Final Results ===
Max positive deviation: 100
Max negative deviation: -30
High - The compounding nature of this issue leads to increasingly severe deviations: