Skip to content

test: add fee forwarding after swap test case#1705

Draft
maxklenk wants to merge 2 commits intomainfrom
fee-after-swap
Draft

test: add fee forwarding after swap test case#1705
maxklenk wants to merge 2 commits intomainfrom
fee-after-swap

Conversation

@maxklenk
Copy link
Copy Markdown
Contributor

@maxklenk maxklenk commented Apr 9, 2026

Test how flexible we can collect fees in different tokens/order in our processes.

@lifi-action-bot lifi-action-bot marked this pull request as draft April 9, 2026 10:40
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

Walkthrough

Added FeeForwarder usage and a new test: deploys FeeForwarder in setup, whitelists its selector, adds a helper that builds a two-step swap (DAI→USDC then feeForwarder.forwardERC20Fees), and a test asserting ERC20 fee forwarding after an ERC20→ERC20 swap.

Changes

Cohort / File(s) Summary
Test ERC20 Fee Collection
test/solidity/Facets/GenericSwapFacetV3.t.sol
Imported FeeForwarder and added feeForwarder state; deployed and whitelisted selector in setUp(); added _produceSwapDataMultiswapERC20SwapAndERC20FeeAfter() helper to build 2-step swap data (DAI→USDC then forwardERC20Fees call, adjusting minAmountOut for integrator fee); added test_CanCollectERC20FeesAfterSwapToERC20_V2() test invoking swapTokensMultipleV3ERC20ToERC20() and expecting LiFiGenericSwapCompleted.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is minimal and generic. It lacks required sections from the template including Linear task reference, implementation rationale, and incomplete checklists with all items unchecked. Complete the PR description template by: (1) Adding the Linear task reference, (2) Explaining the implementation approach, (3) Checking relevant self-review items in the author checklist, (4) Filling audit details in the reviewer checklist.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a test case for fee forwarding after swaps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fee-after-swap

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
test/solidity/Facets/GenericSwapFacetV3.t.sol (2)

1644-1656: Clarify the fromAmount value for the fee forwarding step.

The fromAmount at line 1649 is set to minAmountOut - 10000, but this appears inconsistent with the fee amounts being forwarded:

  • integratorFee = 10 ** usdc.decimals() = 1,000,000 (1 USDC)
  • fromAmount = minAmountOut - 10000 subtracts only 10,000 (0.01 USDC)

While this works (the approval will exceed the transfer amount), it's semantically confusing. Consider using the total fees being forwarded as fromAmount for clarity:

♻️ Suggested clarification
         swapData[1] = LibSwap.SwapData(
             address(feeForwarder),
             address(feeForwarder),
             ADDRESS_USDC,
             ADDRESS_USDC,
-            minAmountOut - 10000,
+            integratorFee,
             abi.encodeWithSelector(
                 feeForwarder.forwardERC20Fees.selector,
                 ADDRESS_USDC,
                 distributions
             ),
             false
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/solidity/Facets/GenericSwapFacetV3.t.sol` around lines 1644 - 1656, The
fromAmount in the second swap step (the LibSwap.SwapData entry in swapData[1])
is set to minAmountOut - 10000 which is semantically misleading given the fees
being forwarded; change the fromAmount to explicitly reflect the actual fee sum
being forwarded (e.g., the integratorFee or the sum of values in distributions)
so readers can see the approval/transfer intent; update the
LibSwap.SwapData.fromAmount for the entry that calls
feeForwarder.forwardERC20Fees.selector to use the total fees variable (or
compute sum(distributions)) instead of minAmountOut - 10000.

1662-1705: Add balance assertions to verify token flows.

The test verifies the event emission and successful execution but lacks assertions to confirm the fee was actually forwarded correctly. Consider adding balance checks similar to other tests in this file (e.g., test_leavesNoERC20SendingAssetDustMultiSwap):

♻️ Suggested balance assertions
         uint256 gasUsed = gasLeftBef - gasleft();
         emit log_named_uint("gas used V2: ", gasUsed);
 
+        // Verify integrator received the fee
+        assertEq(usdc.balanceOf(address(0xb33f)), 10 ** usdc.decimals());
+
+        // Verify receiver got the expected output minus fees
+        assertGe(usdc.balanceOf(SOME_WALLET), minAmountOut);
+
+        // Verify no tokens left in diamond
+        assertEq(usdc.balanceOf(address(genericSwapFacet)), 0);
+
         vm.stopPrank();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/solidity/Facets/GenericSwapFacetV3.t.sol` around lines 1662 - 1705,
Capture balances before and after calling
genericSwapFacetV3.swapTokensMultipleV3ERC20ToERC20 and assert the token flows:
record dai.balanceOf(DAI_HOLDER) and usdc.balanceOf(SOME_WALLET) (and optionally
dai/usdc balance of address(diamond) or genericSwapFacet if relevant) before the
swap, call swapTokensMultipleV3ERC20ToERC20, then assert DAI_HOLDER decreased by
the expected fromAmount and SOME_WALLET increased by the expected fee/received
amount (use amountIn/minAmountOut or the exact fee value your helper
_produceSwapDataMultiswapERC20SwapAndERC20FeeAfter implies); mirror the pattern
used in test_leavesNoERC20SendingAssetDustMultiSwap to ensure no dust remains
and the fee was forwarded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/solidity/Facets/GenericSwapFacetV3.t.sol`:
- Around line 1644-1656: The fromAmount in the second swap step (the
LibSwap.SwapData entry in swapData[1]) is set to minAmountOut - 10000 which is
semantically misleading given the fees being forwarded; change the fromAmount to
explicitly reflect the actual fee sum being forwarded (e.g., the integratorFee
or the sum of values in distributions) so readers can see the approval/transfer
intent; update the LibSwap.SwapData.fromAmount for the entry that calls
feeForwarder.forwardERC20Fees.selector to use the total fees variable (or
compute sum(distributions)) instead of minAmountOut - 10000.
- Around line 1662-1705: Capture balances before and after calling
genericSwapFacetV3.swapTokensMultipleV3ERC20ToERC20 and assert the token flows:
record dai.balanceOf(DAI_HOLDER) and usdc.balanceOf(SOME_WALLET) (and optionally
dai/usdc balance of address(diamond) or genericSwapFacet if relevant) before the
swap, call swapTokensMultipleV3ERC20ToERC20, then assert DAI_HOLDER decreased by
the expected fromAmount and SOME_WALLET increased by the expected fee/received
amount (use amountIn/minAmountOut or the exact fee value your helper
_produceSwapDataMultiswapERC20SwapAndERC20FeeAfter implies); mirror the pattern
used in test_leavesNoERC20SendingAssetDustMultiSwap to ensure no dust remains
and the fee was forwarded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d482f911-5876-4548-8986-83fb82db579b

📥 Commits

Reviewing files that changed from the base of the PR and between df3cf16 and 51346c5.

📒 Files selected for processing (1)
  • test/solidity/Facets/GenericSwapFacetV3.t.sol

@lifi-action-bot
Copy link
Copy Markdown
Collaborator

Test Coverage Report

Line Coverage: 87.33% (3007 / 3443 lines)
Function Coverage: 90.51% ( 477 / 527 functions)
Branch Coverage: 67.07% ( 497 / 741 branches)
Test coverage (87.33%) is above min threshold (83%). Check passed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
test/solidity/Facets/GenericSwapFacetV3.t.sol (4)

1659-1661: Remove extra blank lines.

There are multiple consecutive blank lines before and after the test function. Per coding guidelines, use a single blank line between function declarations.

Suggested fix
         minAmountOut = minAmountOut - integratorFee;
     }
 
-
     function test_CanCollectERC20FeesAfterSwapToERC20_V2() public {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/solidity/Facets/GenericSwapFacetV3.t.sol` around lines 1659 - 1661,
Remove the extra blank lines surrounding the test function in
test/solidity/Facets/GenericSwapFacetV3.t.sol so that there is only a single
blank line between consecutive function declarations; locate the trailing blank
lines after the test function (near the end of the file where the closing brace
for the test is present) and collapse them down to one blank line to comply with
the coding guideline.

1705-1707: Remove extra blank lines after test function.

There are multiple consecutive blank lines after the test function.

Suggested fix
         vm.stopPrank();
     }
 
-
     // MULTISWAP COLLECT NATIVE FEE AND SWAP TO ERC20
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/solidity/Facets/GenericSwapFacetV3.t.sol` around lines 1705 - 1707,
There are multiple consecutive blank lines after the end of a test function in
test/solidity/Facets/GenericSwapFacetV3.t.sol — remove the extra blank lines
immediately following the function closing brace (the trailing "}" that ends the
test) so there's only a single newline separating this function from the next
code block; in short, trim consecutive blank lines after the test function's
closing brace to one.

1634-1636: Remove extra blank line.

There are two consecutive blank lines between the swap data assignments. Per coding guidelines, use a single blank line between logical sections.

Suggested fix
         true
         );
 
-
         // Swap2: Forward ERC20 fees via FeeForwarder
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/solidity/Facets/GenericSwapFacetV3.t.sol` around lines 1634 - 1636,
There is an extra blank line between the Swap2 comment ("// Swap2: Forward ERC20
fees via FeeForwarder") and the surrounding swap data assignments; remove the
extra empty line so only a single blank line separates logical sections (ensure
the "// Swap2: Forward ERC20 fees via FeeForwarder" comment sits with a single
blank line above/below the adjacent swap assignment code in the
GenericSwapFacetV3 test around the Swap2 setup).

1662-1705: Consider adding balance assertions to verify fee forwarding behavior.

The test only verifies event emission but doesn't assert the actual state changes. Given the commit message emphasizes "only forward exact fee to forwarder", adding balance checks would strengthen the test coverage:

  1. Verify the integrator (0xb33f) received exactly integratorFee
  2. Verify SOME_WALLET received minAmountOut of USDC
  3. Verify the diamond has no USDC dust remaining
Suggested assertions
         uint256 gasUsed = gasLeftBef - gasleft();
         emit log_named_uint("gas used V2: ", gasUsed);
 
+        // Verify fee was forwarded to integrator
+        assertEq(
+            usdc.balanceOf(address(0xb33f)),
+            1 * 10 ** usdc.decimals(),
+            "Integrator should receive exact fee"
+        );
+
+        // Verify receiver got the expected amount
+        assertEq(
+            usdc.balanceOf(SOME_WALLET),
+            minAmountOut,
+            "Receiver should receive minAmountOut"
+        );
+
+        // Verify no dust left in diamond
+        assertEq(
+            usdc.balanceOf(address(genericSwapFacet)),
+            0,
+            "Diamond should have no USDC dust"
+        );
+
         vm.stopPrank();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/solidity/Facets/GenericSwapFacetV3.t.sol` around lines 1662 - 1705, The
test test_CanCollectERC20FeesAfterSwapToERC20_V2 currently only checks the
event; add assertions after calling
genericSwapFacetV3.swapTokensMultipleV3ERC20ToERC20 to verify actual balances:
assert the integrator address (0xb33f or the constant used for integrator)
received exactly integratorFee, assert SOME_WALLET's USDC balance increased by
minAmountOut, and assert the diamond contract's USDC balance is zero (no dust).
Use the ERC20 balanceOf on ADDRESS_USDC (and the integrator
address/SOME_WALLET/diamond) and existing assertEq helpers to perform these
checks; reference the test function name and variables integratorFee,
minAmountOut, SOME_WALLET, ADDRESS_USDC, and diamond to locate where to insert
the assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/solidity/Facets/GenericSwapFacetV3.t.sol`:
- Around line 1659-1661: Remove the extra blank lines surrounding the test
function in test/solidity/Facets/GenericSwapFacetV3.t.sol so that there is only
a single blank line between consecutive function declarations; locate the
trailing blank lines after the test function (near the end of the file where the
closing brace for the test is present) and collapse them down to one blank line
to comply with the coding guideline.
- Around line 1705-1707: There are multiple consecutive blank lines after the
end of a test function in test/solidity/Facets/GenericSwapFacetV3.t.sol — remove
the extra blank lines immediately following the function closing brace (the
trailing "}" that ends the test) so there's only a single newline separating
this function from the next code block; in short, trim consecutive blank lines
after the test function's closing brace to one.
- Around line 1634-1636: There is an extra blank line between the Swap2 comment
("// Swap2: Forward ERC20 fees via FeeForwarder") and the surrounding swap data
assignments; remove the extra empty line so only a single blank line separates
logical sections (ensure the "// Swap2: Forward ERC20 fees via FeeForwarder"
comment sits with a single blank line above/below the adjacent swap assignment
code in the GenericSwapFacetV3 test around the Swap2 setup).
- Around line 1662-1705: The test test_CanCollectERC20FeesAfterSwapToERC20_V2
currently only checks the event; add assertions after calling
genericSwapFacetV3.swapTokensMultipleV3ERC20ToERC20 to verify actual balances:
assert the integrator address (0xb33f or the constant used for integrator)
received exactly integratorFee, assert SOME_WALLET's USDC balance increased by
minAmountOut, and assert the diamond contract's USDC balance is zero (no dust).
Use the ERC20 balanceOf on ADDRESS_USDC (and the integrator
address/SOME_WALLET/diamond) and existing assertEq helpers to perform these
checks; reference the test function name and variables integratorFee,
minAmountOut, SOME_WALLET, ADDRESS_USDC, and diamond to locate where to insert
the assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 53570718-ae4a-47bb-bb39-0289a71a9979

📥 Commits

Reviewing files that changed from the base of the PR and between 51346c5 and 516c7e9.

📒 Files selected for processing (1)
  • test/solidity/Facets/GenericSwapFacetV3.t.sol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants