test(pool-cl): testSwap's InvalidFeeForExactOut expectation was dead code - #3
Merged
Merged
Conversation
…code
CI went red on `infinity-core#2` - a PR that touched a workflow file and
a comment and ZERO Solidity - with `testSwap` reporting
`InvalidFeeForExactOut()` after 8,194 runs. The same suite had passed
two days earlier on a different seed. That is the signature of a hole in
a test's own expectations rather than a flake, and it is.
The expectation was written as:
if (zeroForOne) { ...price limit... }
else if (!zeroForOne) { ...price limit... }
else if (amountSpecified <= 0 && swapFee == ONE_HUNDRED_PERCENT_FEE)
{ vm.expectRevert(InvalidFeeForExactOut) }
The first two branches are exhaustive, so the third is UNREACHABLE for
every input. The test could therefore never arm that expectation, and
failed - rather than passing - whenever the fuzzer drew a 100% fee
alongside a valid price limit and a non-negative amountSpecified.
Two further bugs were hiding behind the dead branch, and both are fixed:
* the guard read `amountSpecified <= 0`, but the pool reverts when the
swap is NOT exact input and `exactInput` is `amountSpecified < 0`.
The case is `>= 0`. Since the test bounds amountSpecified to
[0, int128.max], the old condition could only ever have matched
exactly zero;
* it compared the LP fee alone against 100%, where the pool compares
`state.swapFee` - the LP fee COMPOSITED with this direction's
protocol fee.
Rewritten to mirror `CLPool.swap`'s own order: the price limit is
checked first and the fee case second, and only one of the two can be
the revert the pool produces.
Proof, rather than a re-run on a kinder seed:
`test_testSwapCoversTheHundredPercentFeeExactOutputCase` replays the
exact CI counterexample with no fuzzer. Its five `Bound result` logs
match the CI trace exactly (1000000, 1019, 256, 6651, 1). It FAILS with
`InvalidFeeForExactOut()` against the unfixed testSwap and passes
against the fixed one.
`src/` is untouched, so `upstream-guard` is unaffected. Full suite at CI
fuzz depth: 844 passed, 0 failed.
⚠️ Upstream carries the same defect; it is not ours. Worth reporting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
danvaneijck
added a commit
that referenced
this pull request
Sep 7, 2026
This PR is CI-only - a workflow file and a comment, zero Solidity - and its Forge Tests job was red on an upstream test defect it did not cause: testSwap's InvalidFeeForExactOut expectation sat in an unreachable else-branch. Fixed on injective by #3. MERGE rather than rebase: a rebase rewrites the fork commits under new SHAs, GitHub reports the PR CONFLICTING and then creates NO workflow runs at all - the guard and the test suite both silently do not run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Unblocks #2, which is red for a reason that has nothing to do with it: that PR touches a workflow file and a comment and zero Solidity,
src/ identical to pinned upstreampasses on it, andtestSwapstill reportedInvalidFeeForExactOut()after 8,194 runs. The same suite passed on 2026-09-04 with a different seed.It is not a flake. It is a hole in the test's own expectations, and it was always there.
The defect
The first two branches are exhaustive, so the third can never execute, for any input. The test could not arm that expectation, and therefore failed rather than passing whenever the fuzzer drew a 100% fee alongside a valid price limit and a non-negative
amountSpecified.Two more bugs were hiding behind the dead branch, and both are fixed here:
amountSpecified <= 0>= 0— the pool reverts when the swap is not exact input, andexactInputisamountSpecified < 0. The test boundsamountSpecifiedto[0, int128.max], so the old condition could only ever have matched exactly zero== 100%state.swapFee— the LP fee composited with this direction's protocol fee —>= 100%, asCLPool.swapdoesRewritten to mirror
CLPool.swap's own order: the price limit is checked first, the fee case second, and only one of the two can be the revert the pool actually produces.The proof, rather than a re-run on a kinder seed
test_testSwapCoversTheHundredPercentFeeExactOutputCasereplays the exact CI counterexample with no fuzzer involved. Its fiveBound resultlogs match the CI trace exactly —1000000, 1019, 256, 6651, 1— so the replay is faithful, not approximate.testSwap:[FAIL: InvalidFeeForExactOut()][PASS]The draw was
lpFee = 1,000,000(100%, the top of the test's own bound) with a positiveamountSpecified, i.e. an exact-output swap. The pool is right to refuse it: a 100% fee consumes the whole input, so no output can be delivered.--match-test testSwapor even to this one suite file changes forge's per-test input stream, and 25 random seeds at 10,000 runs each produced zero failures on the unfixed code that way. So sampling says nothing here in either direction. The case rests on the structural argument above, which needs no sampling: the branch is unreachable by inspection.Checks
FOUNDRY_PROFILE=ci forge test --isolate: 844 passed, 0 failed (CI's failing run was 837 passed + 1 failed)forge fmt --check: cleansrc/is untouched —git diff --name-only -- src/is empty, soupstream-guardis unaffected and the audits still describe the deployed bytecodeUpstream
test/is not covered by the byte-identical rule, which is the only reason it can be fixed here. It is worth reporting to PancakeSwap so the next pin move does not reintroduce it. Not filed yet.🤖 Generated with Claude Code