Skip to content

test: fix uint128 overflow in IndexingMath round-trip fuzz - #57

Closed
PierrickGT wants to merge 1 commit into
feat/new-version-of-common-libsfrom
fm/common-pr55-missing-tests
Closed

PierrickGT wants to merge 1 commit into
feat/new-version-of-common-libsfrom
fm/common-pr55-missing-tests

Conversation

@PierrickGT

Copy link
Copy Markdown
Member

Follow-up to #56. testFuzz_roundTrip fails on roughly 1 in 20 fuzz seeds, so it will break CI intermittently on this branch and on anything stacked on it.

Cause

The test reserves worst-case round-trip inflation headroom with:

uint112 maxRoundTripInflation_ = uint112((_EXP_SCALED_ONE + index - 1) / index);

_EXP_SCALED_ONE is a uint56 and index is a uint128, so Solidity evaluates that expression in uint128. For any index within 1e12 of type(uint128).max the addition overflows, and the test reverts with Panic(0x11) before it ever reaches the assertion it is meant to make. The library itself is fine — this is purely a defect in the test's own arithmetic.

Reproduce on d21c0ef:

forge test --match-test testFuzz_roundTrip \
  --fuzz-seed 0xcc8b978ef816eedcba7c3fcbfcbf0a4d838b9677ddc2a2b8adab28e86df2a514

Counterexample: principal = 1, index = 2**128 - 4.

Approach

Widen the computation to uint256 before the addition. I kept the + index - 1 ceiling-division form rather than bounding index away from its maximum: bounding would paper over the overflow and silently drop the top of the index range from coverage, which is exactly where the round-trip inflation bound is tightest and most worth pinning.

The added test_roundTrip_atMaxIndex calls the fuzz body directly at index == type(uint128).max for both the smallest and largest principal, so the regression is pinned deterministically instead of depending on a seed to rediscover it.

Verification

  • forge test — 373 passed, 0 failed.
  • Failing seed above now passes.
  • FOUNDRY_FUZZ_RUNS=20000 forge test --match-path test/IndexingMath.t.sol — 27 passed, to confirm no other seed-dependent flake is hiding in the file.
  • src/libs/IndexingMath.sol coverage stays at 100.00% lines / 100.00% branches / 100.00% funcs.
  • forge fmt --check does not flag this file. It does report pre-existing diffs across src/ and script/, untouched here.

`_EXP_SCALED_ONE` is a uint56, so Solidity evaluated
`_EXP_SCALED_ONE + index - 1` in uint128. Any index within 1e12 of
`type(uint128).max` overflowed that addition. The test then reverted
with Panic(0x11) before reaching its assertion. This failed the suite
on roughly 1 in 20 fuzz seeds.

Widen the headroom computation to uint256. Pin the top of the index
range with a deterministic regression test.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions

Copy link
Copy Markdown

Changes to gas cost

Generated at commit: 546c6ce6184588084a63cc588447127774eae5c7, compared to commit: 4c9dc2559aefffd031260f8b292b8c71ec3bc308

🧾 Summary (20% most significant diffs)

Contract Method Avg (+/-) %
Bytes32StringHarness toString +266 ❌ +3.37%
ERC20ExtendedHarness mint
transferFrom
-172 ✅
+128 ❌
-0.34%
+0.36%

Full diff report 👇
Contract Deployment Cost (+/-) Method Min (+/-) % Avg (+/-) % Median (+/-) % Max (+/-) % # Calls (+/-)
Bytes32StringHarness 238,480 (0) toBytes32
toString
822 (0)
698 (0)
0.00%
0.00%
846 (0)
8,153 (+266)
0.00%
+3.37%
856 (0)
10,311 (+660)
0.00%
+6.84%
858 (+2)
11,261 (0)
+0.23%
0.00%
293 (0)
293 (0)
ERC20ExtendedHandler 766,606 (0) approve
burn
mint
transfer
transferFrom
31,355 (0)
41,828 (0)
381 (0)
477 (0)
488 (0)
0.00%
0.00%
0.00%
0.00%
0.00%
45,864 (+141)
46,143 (-87)
50,373 (-210)
58,163 (+449)
54,434 (+16)
+0.31%
-0.19%
-0.42%
+0.78%
+0.03%
51,303 (0)
44,691 (0)
62,063 (0)
60,896 (0)
60,925 (0)
0.00%
0.00%
0.00%
0.00%
0.00%
51,879 (0)
53,627 (0)
96,839 (0)
131,325 (-87)
133,866 (-129)
0.00%
0.00%
0.00%
-0.07%
-0.10%
12,991 (+70)
12,722 (-113)
13,015 (+66)
12,749 (-238)
12,774 (+215)
ERC20ExtendedUpgradeableHarness 1,964,420 (0) approve
mint
permit
transferFrom
4,725 (0)
7,154 (0)
862 (0)
2,936 (0)
0.00%
0.00%
0.00%
0.00%
24,509 (+135)
46,689 (+99)
40,459 (-167)
15,289 (+98)
+0.55%
+0.21%
-0.41%
+0.65%
24,625 (0)
46,954 (0)
55,898 (0)
8,262 (0)
0.00%
0.00%
0.00%
0.00%
24,625 (0)
46,954 (0)
56,167 (0)
35,516 (0)
0.00%
0.00%
0.00%
0.00%
1,033 (0)
3,612 (0)
1,548 (0)
775 (0)
ERC20ExtendedHarness 1,677,854 (0) approve
burn
mint
permit
receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)
receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes32,bytes32)
receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)
transfer
transferFrom
transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)
transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes32,bytes32)
transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)
26,013 (0)
24,120 (+12)
28,461 (0)
23,844 (0)
61,106 (+352)
60,251 (+352)
29,183 (0)
24,109 (0)
24,594 (0)
60,958 (+372)
60,235 (+372)
29,086 (0)
0.00%
+0.05%
0.00%
0.00%
+0.58%
+0.59%
0.00%
0.00%
0.00%
+0.61%
+0.62%
0.00%
41,237 (+127)
29,602 (-66)
50,378 (-172)
60,321 (-158)
83,648 (+19)
82,793 (+19)
80,266 (+18)
31,626 (-10)
35,533 (+128)
83,488 (+17)
82,766 (+18)
80,320 (+17)
+0.31%
-0.22%
-0.34%
-0.26%
+0.02%
+0.02%
+0.02%
-0.03%
+0.36%
+0.02%
+0.02%
+0.02%
45,985 (+12)
28,711 (0)
51,269 (-120)
74,964 (+3)
83,830 (+4)
82,975 (+4)
83,192 (+8)
28,871 (0)
31,873 (0)
83,678 (+12)
82,955 (+12)
83,058 (+4)
+0.03%
0.00%
-0.23%
+0.00%
+0.00%
+0.00%
+0.01%
0.00%
0.00%
+0.01%
+0.01%
+0.00%
46,537 (0)
34,695 (0)
68,873 (0)
75,893 (+12)
84,222 (0)
83,367 (0)
83,588 (0)
51,955 (0)
57,733 (-36)
84,062 (0)
83,339 (0)
83,450 (0)
0.00%
0.00%
0.00%
+0.02%
0.00%
0.00%
0.00%
0.00%
-0.06%
0.00%
0.00%
0.00%
14,809 (+62)
13,237 (-113)
15,332 (+61)
1,548 (0)
258 (0)
258 (0)
272 (0)
12,008 (-118)
11,129 (+163)
258 (0)
258 (0)
271 (0)
TransferHelperHarness 472,638 (0) safeApprove
safeTransfer
safeTransferExact
safeTransferExactFrom
safeTransferFrom
25,909 (0)
28,310 (0)
32,918 (0)
36,230 (0)
34,039 (0)
0.00%
0.00%
0.00%
0.00%
0.00%
37,169 (+39)
40,805 (-141)
68,018 (-133)
74,093 (-150)
46,929 (-157)
+0.11%
-0.34%
-0.20%
-0.20%
-0.33%
27,300 (+6)
29,679 (0)
68,274 (0)
74,383 (0)
34,839 (0)
+0.02%
0.00%
0.00%
0.00%
0.00%
48,875 (0)
53,780 (0)
80,919 (0)
87,024 (0)
59,931 (0)
0.00%
0.00%
0.00%
0.00%
0.00%
1,028 (0)
1,028 (0)
514 (0)
514 (0)
1,028 (0)
ContractHelperHarness 221,136 (0) getContractFrom 697 (0) 0.00% 754 (+1) +0.13% 768 (0) 0.00% 781 (0) 0.00% 270 (0)
Proxy 0 (0) fallback 5,070 (0) 0.00% 37,901 (+19) +0.05% 16,681 (0) 0.00% 165,559 (0) 0.00% 19,346 (0)
SignatureCheckerHarness 618,072 (0) isValidECDSASignature(address,bytes32,bytes)
isValidECDSASignature(address,bytes32,bytes32,bytes32)
isValidSignature
recoverECDSASigner(bytes32,bytes)
recoverECDSASigner(bytes32,bytes32,bytes32)
recoverECDSASigner(bytes32,uint8,bytes32,bytes32)
validateECDSASignature(address,bytes32,bytes)
validateECDSASignature(address,bytes32,bytes32,bytes32)
1,349 (0)
938 (0)
4,436 (0)
1,147 (0)
805 (0)
742 (0)
1,230 (0)
861 (0)
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
4,843 (-1)
4,446 (0)
4,965 (-1)
4,548 (-1)
4,219 (-1)
4,143 (-1)
4,724 (-1)
4,375 (-1)
-0.02%
0.00%
-0.02%
-0.02%
-0.02%
-0.02%
-0.02%
-0.02%
4,861 (-20)
4,450 (-20)
4,952 (0)
4,565 (-20)
4,223 (-20)
4,160 (-20)
4,742 (-20)
4,373 (-20)
-0.41%
-0.45%
0.00%
-0.44%
-0.47%
-0.48%
-0.42%
-0.46%
4,897 (0)
4,486 (0)
8,729 (0)
4,585 (0)
4,243 (0)
4,180 (0)
4,778 (0)
4,409 (0)
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
0.00%
263 (0)
263 (0)
534 (0)
261 (0)
260 (0)
261 (0)
263 (0)
520 (0)

@github-actions

Copy link
Copy Markdown

LCOV of commit dd6b86d during Forge Coverage #182

Summary coverage rate:
  lines......: 95.4% (476 of 499 lines)
  functions..: 95.6% (153 of 160 functions)
  branches...: no data found

Files changed coverage rate: n/a

@PierrickGT

Copy link
Copy Markdown
Member Author

Closing since the issue was addressed in 10702a0

@PierrickGT PierrickGT closed this Sep 17, 2026
@PierrickGT
PierrickGT deleted the fm/common-pr55-missing-tests branch September 17, 2026 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants