Skip to content

Conversation

lumtis
Copy link
Member

@lumtis lumtis commented Aug 19, 2025

Closes #42

The check source == target is done in swap module instead of the router contract

Summary by CodeRabbit

  • New Features
    • Always route settlements through the swap module, enabling consistent fee/slippage handling.
    • Support same-token “swap” flows with gas-fee deduction.
    • Add direct-pool fast path for eligible pairs to improve execution efficiency.
  • Improvements
    • Apply slippage and fee adjustments consistently after swap execution.
    • More robust handling of edge cases (e.g., zero effective input after gas fees).
  • Tests
    • Expanded coverage for same-token scenarios and gas-fee behaviors.
    • Adjusted assertions for precision and updated event expectations.

@lumtis lumtis requested a review from s2imonovic August 19, 2025 15:18
Copy link

codecov bot commented Aug 19, 2025

Codecov Report

❌ Patch coverage is 86.66667% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/swapModules/SwapAlgebra.sol 85.00% 3 Missing ⚠️
src/Router.sol 90.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link

coderabbitai bot commented Aug 19, 2025

📝 Walkthrough

Walkthrough

The Router now always invokes the swap module, removing the no-swap branch for same-token cases. SwapAlgebra adds a same-token early exit and direct-pool fast-path, consolidating slippage checks post-swap. MockSwapModule switches to gas-fee-first deduction and adds an overload. Tests are updated/added to cover same-token and gas-fee behaviors.

Changes

Cohort / File(s) Summary
Router: Always route via swap
src/Router.sol
Removed no-swap branch; unconditional approvals and swap call. Unified slippage/fee adjustment post-swap affecting tipAfterSwap and actualAmount.
SwapAlgebra: Same-token & routing refactor
src/swapModules/SwapAlgebra.sol
Early exit for tokenIn==tokenOut; direct-pool fast-path; fallback to two-hop via intermediary; centralized final balance/slippage check and transfer.
Mock swap module: Gas-fee-first model
test/mocks/MockSwapModule.sol
Deduct gasFee upfront, return gas tokens immediately, apply slippage on post-gas amount; handles zero post-gas; adds overload with identical logic.
Router tests: Same-token path uses swap
test/Router.t.sol
Renamed test to reflect gas-fee handling; removes no-swap assertions; expects tip reduced by gasFee and swap module involvement.
SwapAlgebra tests: New same-token cases
test/swapModules/SwapAlgebra.t.sol
Adds tests for same-token swap and same token as gas; minor arithmetic precedence fixes in min amount calculations.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Router
  participant SwapModule
  participant Pool as DEX/Pool(s)

  User->>Router: processIntent(intentInfo, settlementInfo)
  Note over Router: Always approve & call swap
  Router->>SwapModule: swap(zrc20, targetZRC20, amountWithTip, gasZRC20, gasFee, ...)
  alt tokenIn == tokenOut
    SwapModule-->>Router: amountWithTipOut (post gas/slippage)
  else Direct pool
    SwapModule->>Pool: swap tokenIn -> tokenOut
    Pool-->>SwapModule: amountOut
    SwapModule-->>Router: amountWithTipOut
  end
  Note over Router: Adjust tipAfterSwap and actualAmount for slippage/fees
  Router-->>User: Emit settlement event
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Deduct gas fee from tip when source equals target asset (#42)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Introduce direct-pool fast-path and reorganized multi-hop routing (src/swapModules/SwapAlgebra.sol, lines n/a) Not required to fix same-token gas deduction in Router; extends routing logic beyond the stated objective.
Add overload swap(address,address,uint256,address,uint256,string) (test/mocks/MockSwapModule.sol, lines n/a) Additional API not referenced in the objective; unrelated to ensuring gas deduction on same-token paths.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/gas-payment-no-swap

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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.

Actionable comments posted: 0

🧹 Nitpick comments (5)
src/swapModules/SwapAlgebra.sol (1)

244-246: Consider adding event emission for missing intermediary token

The require statement correctly validates the intermediary token, but consider emitting an event before reverting to aid in debugging and monitoring when this error occurs in production.

 // Get the intermediary token for this token name
 address intermediaryToken = intermediaryTokens[tokenName];
+if (intermediaryToken == address(0)) {
+    emit IntermediaryTokenMissing(tokenName, tokenIn, tokenOut);
+}
 require(intermediaryToken != address(0), "No intermediary token set for this token name");

Add the event definition near line 49:

event IntermediaryTokenMissing(string indexed tokenName, address indexed tokenIn, address indexed tokenOut);
test/Router.t.sol (1)

1339-1340: Consider extracting magic numbers into named constants

The minted amount calculation amount + tip - gasFee could be clearer with a named constant or variable to improve test readability.

+// Calculate expected amount after gas fee for same-token case
+uint256 expectedSwapModuleAmount = amount + tip - gasFee;
 // Input token also goes to swap module for the main swap (same token case)
-inputToken.mint(address(swapModule), amount + tip - gasFee);
+inputToken.mint(address(swapModule), expectedSwapModuleAmount);
test/swapModules/SwapAlgebra.t.sol (1)

589-619: Consider adding assertion for zero pool calls

While the comment mentions "no pool interaction," consider adding an explicit assertion to verify that the mock pool's swap function was not called.

Add a call counter to the MockAlgebraPool contract and verify it remains zero:

 // Verify that no actual swap was performed (no pool interaction)
 // The mock pool should not have been called since it's the same token
+MockAlgebraPool pool = MockAlgebraPool(mockAlgebraFactory.poolByPair(address(inputToken), address(inputToken)));
+assertEq(pool.swapCallCount(), 0, "Pool swap should not be called for same token");
src/Router.sol (1)

389-390: Consider addressing the surplus handling TODO

The comment indicates that surplus amounts are currently ignored. While acknowledged as a future improvement, consider adding a simple event emission to track when surpluses occur for monitoring purposes.

 // Note: Surplus amounts are currently ignored as they are too small to handle
 // This will be addressed in future updates
+if (settlementInfo.amountWithTipOut > wantedAmountWithTip) {
+    emit SwapSurplusDetected(
+        intentInfo.zrc20,
+        settlementInfo.targetZRC20,
+        settlementInfo.amountWithTipOut - wantedAmountWithTip
+    );
+}

Add the event definition near line 105:

event SwapSurplusDetected(address indexed tokenIn, address indexed tokenOut, uint256 surplusAmount);
test/mocks/MockSwapModule.sol (1)

78-126: Consider extracting duplicated swap logic

The swap implementation is duplicated between the two function overloads. Consider extracting the common logic to a private function to improve maintainability.

+function _performSwap(
+    address tokenIn,
+    address tokenOut,
+    uint256 amountIn,
+    address gasZRC20,
+    uint256 gasFee
+) private returns (uint256 amountOut) {
+    // Transfer tokens from sender to this contract
+    IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
+
+    // Handle gas fee first (similar to SwapAlgebra)
+    uint256 amountInForMainSwap = amountIn;
+
+    if (gasFee > 0 && gasZRC20 != address(0)) {
+        // If tokenIn is not the gas token, we need to account for gas fee
+        if (tokenIn != gasZRC20) {
+            // In mock, we just reduce the main swap amount by gas fee
+            amountInForMainSwap = amountIn - gasFee;
+        } else {
+            // If tokenIn is already the gas token, just set aside the needed amount
+            amountInForMainSwap = amountIn - gasFee;
+        }
+
+        // Transfer gas fee tokens back to the sender
+        IERC20(gasZRC20).transfer(msg.sender, gasFee);
+    }
+
+    // Handle main swap
+    if (amountInForMainSwap > 0) {
+        // Check if input and output tokens are the same
+        if (tokenIn == tokenOut) {
+            // No swap needed, just return the remaining amount
+            amountOut = amountInForMainSwap;
+        } else {
+            // Calculate amount out based on slippage settings
+            uint256 slippageCost = (amountInForMainSwap * slippage) / 10000;
+            amountOut = amountInForMainSwap - slippageCost;
+        }
+
+        // Transfer tokens back to the sender
+        IERC20(tokenOut).transfer(msg.sender, amountOut);
+    } else {
+        amountOut = 0;
+    }
+
+    // Allow to set a custom amount out for testing
+    if (customAmountOut > 0) {
+        amountOut = customAmountOut;
+    }
+
+    return amountOut;
+}
+
 function swap(address tokenIn, address tokenOut, uint256 amountIn, address gasZRC20, uint256 gasFee)
     external
     override
     returns (uint256 amountOut)
 {
-    // [Current implementation]
+    return _performSwap(tokenIn, tokenOut, amountIn, gasZRC20, gasFee);
 }

 function swap(address tokenIn, address tokenOut, uint256 amountIn, address gasZRC20, uint256 gasFee, string memory)
     external
     returns (uint256 amountOut)
 {
-    // [Current implementation]
+    return _performSwap(tokenIn, tokenOut, amountIn, gasZRC20, gasFee);
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 14b0cde and eeaf5cd.

📒 Files selected for processing (5)
  • src/Router.sol (1 hunks)
  • src/swapModules/SwapAlgebra.sol (1 hunks)
  • test/Router.t.sol (4 hunks)
  • test/mocks/MockSwapModule.sol (2 hunks)
  • test/swapModules/SwapAlgebra.t.sol (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.sol

⚙️ CodeRabbit Configuration File

Review the Solidity smart contract code, focusing on security vulnerabilities, gas optimization, best practices, and potential reentrancy issues. Check for proper access controls, input validation, and adherence to established patterns like CEI (Checks-Effects-Interactions).

Files:

  • test/swapModules/SwapAlgebra.t.sol
  • src/Router.sol
  • test/mocks/MockSwapModule.sol
  • test/Router.t.sol
  • src/swapModules/SwapAlgebra.sol
**/test/**/*.sol

⚙️ CodeRabbit Configuration File

Review the Solidity test files, ensuring comprehensive test coverage, proper edge case handling, and correct use of Foundry testing patterns. Check for proper setup, teardown, and assertion patterns.

Files:

  • test/swapModules/SwapAlgebra.t.sol
  • test/mocks/MockSwapModule.sol
  • test/Router.t.sol
🔇 Additional comments (8)
src/swapModules/SwapAlgebra.sol (2)

262-277: Good refactoring of balance finalization logic

The consolidation of slippage checks and balance calculations after all swap paths is a clean architectural improvement. This ensures consistent handling regardless of whether a direct pool or intermediary path is taken.


230-235: Potential reentrancy vulnerability in same-token transfer path

When tokenIn == tokenOut, the contract transfers tokens directly back to the sender without following the checks-effects-interactions pattern. The amountOut state is set after the external transfer call, which could allow reentrancy attacks if the token is malicious or has hooks.

Apply this diff to follow the checks-effects-interactions pattern:

 // Check if input and output tokens are the same
 if (tokenIn == tokenOut) {
     // No swap needed, just transfer the remaining amount back to sender
-    amountOut = amountInForMainSwap;
-    IERC20(tokenOut).safeTransfer(msg.sender, amountOut);
+    uint256 transferAmount = amountInForMainSwap;
+    amountOut = transferAmount;
+    IERC20(tokenOut).safeTransfer(msg.sender, transferAmount);

Additionally, consider adding a reentrancy guard to the entire swap function for defense in depth.

Likely an incorrect or invalid review comment.

test/Router.t.sol (1)

1298-1375: Well-structured test for same-token gas fee handling

The test correctly validates the gas fee deduction behavior when source and target tokens are identical. The event assertions and balance checks properly verify that gas fees are deducted from the tip as expected.

test/swapModules/SwapAlgebra.t.sol (2)

557-587: Comprehensive test coverage for same-token swaps

Excellent addition of test cases for same-token swap scenarios. The tests properly verify that no pool interaction occurs and that gas fees are correctly handled.


519-519: Good fix for operator precedence clarity

Adding explicit parentheses improves code clarity and ensures the multiplication is performed before division, preventing potential precision loss.

src/Router.sol (2)

372-385: Clean implementation of unconditional swap routing

The removal of the conditional branch and always routing through the swap module simplifies the code flow and ensures consistent gas fee handling. The double approval pattern (first to 0, then to amount) is a good practice to handle tokens with non-standard approval behaviors.


387-408: Well-implemented slippage and fee cost handling

The logic correctly handles both scenarios where the tip covers costs and where it doesn't. The require statement provides appropriate protection against insufficient funds.

test/mocks/MockSwapModule.sol (1)

32-47: Good simulation of gas fee handling

The mock correctly simulates the gas fee deduction logic, making it suitable for testing the Router's behavior with different token scenarios.

@lumtis lumtis merged commit 9ddc124 into main Aug 20, 2025
3 checks passed
@lumtis lumtis deleted the fix/gas-payment-no-swap branch August 20, 2025 14:13
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.

Gas fee not deducted from tip when source asset is same as target asset
1 participant