Skip to content

Commit 95b1768

Browse files
authored
Statistics.t.sol: Add statistic fuzz tests (#27)
* Statistics.t.sol: Add statistic fuzz tests * Statistic.t.sol: Add more fuzz test * Statistics.t.sol: remove empty arr test
1 parent 5f5b5f6 commit 95b1768

File tree

1 file changed

+128
-32
lines changed

1 file changed

+128
-32
lines changed

test/Statistics.t.sol

Lines changed: 128 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ contract StatisticsTest is Test {
112112
data[3] = number4;
113113

114114
uint256 average = Statistics.avg(data);
115-
// console.log("Average: ", average);
115+
116+
assert(average >= Math.min(Math.min(number1, number2), Math.min(number3, number4)) * 1 ether);
117+
assert(average <= Math.max(Math.max(number1, number2), Math.max(number3, number4)) * 1 ether);
116118
}
117119

118120
function testFuzz_Variance(uint8 number1, uint8 number2, uint8 number3, uint8 number4, uint8 number5)
@@ -133,45 +135,139 @@ contract StatisticsTest is Test {
133135
data[4] = number5;
134136

135137
(uint256 variance,) = Statistics.variance(data);
136-
// console.log("Variance: ", variance);
138+
139+
// variance be 0 if all numbers are equal
140+
if (number1 == number2 && number2 == number3 && number3 == number4 && number4 == number5) {
141+
assertEq(variance, 0);
142+
}
143+
144+
// variance should be non-negative
145+
assert(variance >= 0);
137146
}
138147

139-
function testFuzz_StandardDeviation(
140-
uint8 number1,
141-
uint8 number2,
142-
uint8 number3,
143-
uint8 number4,
144-
uint8 number5,
145-
uint8 number6,
146-
uint8 number7,
147-
uint8 number8,
148-
uint8 number9,
149-
uint8 number10
150-
) external pure {
151-
vm.assume(number1 <= MAX_SCORE && number1 > MIN_SCORE);
152-
vm.assume(number2 <= MAX_SCORE && number2 > MIN_SCORE);
153-
vm.assume(number3 <= MAX_SCORE && number3 > MIN_SCORE);
154-
vm.assume(number4 <= MAX_SCORE && number4 > MIN_SCORE);
155-
vm.assume(number5 <= MAX_SCORE && number5 > MIN_SCORE);
156-
vm.assume(number6 <= MAX_SCORE && number6 > MIN_SCORE);
157-
vm.assume(number7 <= MAX_SCORE && number7 > MIN_SCORE);
158-
vm.assume(number8 <= MAX_SCORE && number8 > MIN_SCORE);
159-
vm.assume(number9 <= MAX_SCORE && number9 > MIN_SCORE);
160-
vm.assume(number10 <= MAX_SCORE && number10 > MIN_SCORE);
161-
162-
uint256[] memory data = new uint256[](10);
148+
function testFuzz_StandardDeviation(uint8 number1, uint8 number2, uint8 number3, uint8 number4, uint8 number5)
149+
external
150+
pure
151+
{
152+
vm.assume(number1 <= MAX_SCORE && number1 >= MIN_SCORE);
153+
vm.assume(number2 <= MAX_SCORE && number2 >= MIN_SCORE);
154+
vm.assume(number3 <= MAX_SCORE && number3 >= MIN_SCORE);
155+
vm.assume(number4 <= MAX_SCORE && number4 >= MIN_SCORE);
156+
vm.assume(number5 <= MAX_SCORE && number5 >= MIN_SCORE);
157+
158+
uint256[] memory data = new uint256[](5);
163159
data[0] = number1;
164160
data[1] = number2;
165161
data[2] = number3;
166162
data[3] = number4;
167163
data[4] = number5;
168-
data[5] = number6;
169-
data[6] = number7;
170-
data[7] = number8;
171-
data[8] = number9;
172-
data[9] = number10;
173164

174165
(uint256 stddev,) = Statistics.stddev(data);
175-
// console.log("Standard Deviation: ", stddev);
166+
167+
// standard deviation should be 0 if all numbers are equal
168+
if (number1 == number2 && number2 == number3 && number3 == number4 && number4 == number5) {
169+
assertEq(stddev, 0);
170+
}
171+
172+
// standard deviation should be non-negative
173+
assert(stddev >= 0);
174+
}
175+
176+
// Test for array bounds
177+
function testFuzz_ArrayBounds(uint8 length) external pure {
178+
// limit array size to prevent overflow and excessive gas costs
179+
vm.assume(length > 0 && length <= 32);
180+
181+
uint256[] memory data = new uint256[](length);
182+
for (uint256 i = 0; i < length; i++) {
183+
data[i] = MIN_SCORE; // use min to avoid overflow
184+
}
185+
186+
uint256 avg = Statistics.avg(data);
187+
assertEq(avg, MIN_SCORE * 1 ether);
188+
189+
(uint256 variance,) = Statistics.variance(data);
190+
assertEq(variance, 0);
191+
}
192+
193+
// Test invariance under translation
194+
function testFuzz_TranslationInvariance(uint8 shift) external pure {
195+
vm.assume(shift <= 50); // Ensure we don't overflow MAX_SCORE
196+
197+
uint256[] memory data10 = new uint256[](3);
198+
uint256[] memory data20 = new uint256[](3);
199+
200+
// original data
201+
data10[0] = 100;
202+
data10[1] = 150;
203+
data10[2] = 200;
204+
205+
// shifted data
206+
data20[0] = 100 + shift;
207+
data20[1] = 150 + shift;
208+
data20[2] = 200 + shift;
209+
210+
(uint256 variance1,) = Statistics.variance(data10);
211+
(uint256 variance2,) = Statistics.variance(data20);
212+
213+
// variance should be invariant under translation
214+
assertApproxEqAbs(variance1, variance2, 1e15);
215+
}
216+
217+
// Test that variance scales correctly when data is multiplied
218+
function testFuzz_ScaleInvariance(uint8 length, uint8 scale) external pure {
219+
vm.assume(length > 0 && length <= 32);
220+
vm.assume(scale > 0 && scale <= 10);
221+
222+
uint256[] memory data = new uint256[](length);
223+
uint256[] memory scaledData = new uint256[](length);
224+
225+
for (uint256 i = 0; i < length; i++) {
226+
data[i] = bound(uint256(uint256(keccak256(abi.encode(i))) % MAX_SCORE), MIN_SCORE, MAX_SCORE / scale);
227+
scaledData[i] = data[i] * scale;
228+
}
229+
230+
(uint256 variance1,) = Statistics.variance(data);
231+
(uint256 variance2,) = Statistics.variance(scaledData);
232+
233+
assertApproxEqAbs(variance2, variance1 * scale * scale, 1e15);
234+
}
235+
236+
function testFuzz_OrderInvariance(uint8 length) external view {
237+
vm.assume(length > 1 && length <= 32);
238+
239+
uint256[] memory data = new uint256[](length);
240+
uint256[] memory shuffledData = new uint256[](length);
241+
242+
for (uint256 i = 0; i < length; i++) {
243+
data[i] = bound(uint256(uint256(keccak256(abi.encode(i))) % MAX_SCORE), MIN_SCORE, MAX_SCORE);
244+
shuffledData[i] = data[i];
245+
}
246+
247+
// Shuffle
248+
for (uint256 i = length - 1; i > 0; i--) {
249+
uint256 j = uint256(keccak256(abi.encodePacked(block.timestamp, i))) % (i + 1);
250+
(shuffledData[i], shuffledData[j]) = (shuffledData[j], shuffledData[i]);
251+
}
252+
253+
uint256 avg1 = Statistics.avg(data);
254+
uint256 avg2 = Statistics.avg(shuffledData);
255+
(uint256 variance1,) = Statistics.variance(data);
256+
(uint256 variance2,) = Statistics.variance(shuffledData);
257+
258+
assertEq(avg1, avg2);
259+
assertEq(variance1, variance2);
260+
}
261+
262+
function testFuzz_ExtremeValues(uint8 length) external pure {
263+
vm.assume(length > 1 && length <= 32); // Must have at least 2 elements
264+
265+
uint256[] memory extremeData = new uint256[](length);
266+
for (uint256 i = 0; i < length; i++) {
267+
extremeData[i] = i % 2 == 0 ? MAX_SCORE : MIN_SCORE;
268+
}
269+
270+
(uint256 variance,) = Statistics.variance(extremeData);
271+
assert(variance > 0);
176272
}
177273
}

0 commit comments

Comments
 (0)