Skip to content

Conversation

@rudewalt
Copy link
Contributor

@rudewalt rudewalt commented Jan 10, 2025

Summary by CodeRabbit

Based on the comprehensive changes, here are the release notes:

  • New Features

    • Added support for advanced Pendle and Curve token swapping adapters
    • Introduced new adapter types for complex token routing and exchange
    • Enhanced router functionality with pre- and post-maturity token swap capabilities
  • Contracts

    • Deployed new smart contracts: PendleCurveNgAdapter and PendleCurveRouterNgAdapter
    • Added interfaces for Curve pool and router interactions
    • Implemented robust error handling for token swaps
  • Testing

    • Expanded test coverage for token swap scenarios
    • Added comprehensive integration tests for various token pairs
    • Implemented pre- and post-maturity swap validation tests
  • Configuration

    • Updated deployment and router configurations
    • Improved Hardhat compiler optimization settings

@coderabbitai
Copy link

coderabbitai bot commented Jan 10, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces comprehensive support for Pendle Curve adapters in the Marginly protocol. The changes span multiple packages, adding new types, interfaces, and smart contracts to facilitate token swaps involving Pendle Principal Tokens (PT) and Interest Bearing (IB) tokens. The modifications enhance the router's flexibility by introducing two new adapter types: PendleCurveNgAdapter and PendleCurveRouterNgAdapter, which enable complex token swapping mechanisms with advanced routing and slippage management.

Changes

File Change Summary
packages/deploy/src/config.ts Added new adapter pair types and interfaces for Pendle Curve adapters
packages/deploy/src/deployer/MarginlyRouterDeployer.ts Updated deployment method to support new Pendle Curve adapter types
packages/router/contracts/adapters/PendleCurveNgAdapter.sol New smart contract for Pendle Curve token swapping
packages/router/contracts/adapters/PendleCurveRouterNgAdapter.sol New smart contract for advanced Pendle Curve token routing
packages/router/test/... Added comprehensive test suites for new adapters with various token pairs

Sequence Diagram

sequenceDiagram
    participant User
    participant MarginlyRouter
    participant PendleCurveAdapter
    participant PendleMarket
    participant CurvePool

    User->>MarginlyRouter: Initiate Token Swap
    MarginlyRouter->>PendleCurveAdapter: Deploy Adapter
    PendleCurveAdapter->>PendleMarket: Check Token Maturity
    PendleMarket-->>PendleCurveAdapter: Maturity Status
    PendleCurveAdapter->>CurvePool: Execute Swap
    CurvePool-->>PendleCurveAdapter: Swap Completed
    PendleCurveAdapter-->>MarginlyRouter: Swap Result
    MarginlyRouter-->>User: Token Swap Confirmed
Loading

Possibly related PRs

Poem

🐰 Hop, hop, through tokens we'll glide,
Pendle and Curve, now side by side!
Adapters dance with slippage so neat,
Swapping tokens, a cryptographic feat!
Router magic, our rabbit's delight! 🌈


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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.
    • Generate unit testing code for this file.
    • 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. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

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

@rudewalt
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 10, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 3

🧹 Nitpick comments (12)
packages/deploy/src/deployer/MarginlyRouterDeployer.ts (1)

78-80: Handle Missing curveRouter Parameter More Gracefully

Currently, the code throws a generic Error when curveRouter is not provided for a PendleCurveRouterAdapter. Consider creating and throwing a custom error type or adding additional context to the error message to improve debuggability and maintainability.

Apply this diff to define and use a custom error:

+ class MissingCurveRouterError extends Error {
+   constructor() {
+     super('CurveRouter address is required for PendleCurveRouterAdapter');
+     this.name = 'MissingCurveRouterError';
+   }
+ }

...

        } else if (isPendleCurveRouterAdapter(pools[0])) {
          if (!curveRouter) {
-           throw new Error('CurveRouter address is required for PendleCurveRouterAdapter');
+           throw new MissingCurveRouterError();
          }
packages/router/contracts/adapters/PendleCurveNgAdapter.sol (1)

476-544: Optimize Looping in _addPairs Function

In the _addPairs function, consider using for loops with proper bounds checking and utilizing unchecked blocks only when you are certain overflow is impossible to enhance performance safely.

Example modification:

-         unchecked {
-           ++i;
-         }
+         if (i < length - 1) {
+           i++;
+         } else {
+           break;
+         }

Ensure that the use of unchecked is justified and documented.

packages/deploy/src/deployer/configs.ts (1)

682-717: Refactor to eliminate redundant runtime checks with type enforcement

With the suggested use of tuples to enforce array lengths at the type level, the runtime checks for the lengths of curveRoute, curvePools, and curveSwapParams in the PendleCurveRouter adapter configuration become unnecessary. Removing these checks can simplify the code and reduce potential performance overhead.

Apply this diff to remove redundant length checks:

-        if (pairConfig.curveRoute.length !== 11) {
-          throw new Error(
-            `Wrong config for Pendle curve router adapter with dexId ${adapter.dexId}. Curve route length must be 11`
-          );
-        }

-        if (pairConfig.curvePools.length !== 5) {
-          throw new Error(
-            `Wrong config for Pendle curve router adapter with dexId ${adapter.dexId}. Curve pools length must be 5`
-          );
-        }

-        if (pairConfig.curveSwapParams.length !== 5) {
-          throw new Error(
-            `Wrong config for Pendle curve router adapter with dexId ${adapter.dexId}. Curve swap params array must be 5x5`
-          );
-        }

-        for (let i = 0; i < 5; i++) {
-          if (pairConfig.curveSwapParams[i].length !== 5) {
-            throw new Error(
-              `Wrong config for Pendle curve router adapter with dexId ${adapter.dexId}. Curve swap params array must be 5x5`
-            );
-          }
-        }
packages/router/contracts/adapters/interfaces/ICurveRouterNg.sol (1)

5-26: Optimize external function parameters by using calldata

In the ICurveRouterNg interface, the functions exchange, get_dy, and get_dx use memory for array parameters. Since these functions are external, using calldata instead of memory for array parameters can be more gas-efficient and avoids unnecessary data copying.

Apply this diff to change memory to calldata:

 interface ICurveRouterNg {
   function exchange(
-    address[11] memory route,
-    uint256[5][5] memory swapParams,
+    address[11] calldata route,
+    uint256[5][5] calldata swapParams,
     uint256 amount,
     uint256 minDy,
-    address[5] memory pools,
+    address[5] calldata pools,
     address receiver
   ) external returns (uint256);

   function get_dy(
-    address[11] memory route,
-    uint256[5][5] memory swapParams,
+    address[11] calldata route,
+    uint256[5][5] calldata swapParams,
     uint256 amount
   ) external view returns (uint256);

   function get_dx(
-    address[11] memory route,
-    uint256[5][5] memory swapParams,
     uint256 outAmount,
-    address[5] memory pools
+    address[11] calldata route,
+    uint256[5][5] calldata swapParams,
+    uint256 outAmount,
+    address[5] calldata pools
   ) external view returns (uint256);
 }
packages/router/contracts/adapters/interfaces/ICurvePool.sol (1)

37-37: Consider adding NatSpec documentation for get_dx.

The get_dx function lacks documentation unlike other interface methods.

Add NatSpec documentation similar to get_dy:

+  /*
+        @notice Calculate the required input dx given output `dy`
+        @dev Index values can be found via the `coins` public getter method
+        @param i Index value for the coin to send
+        @param j Index value of the coin to receive
+        @param dy Desired amount of `j` to receive
+        @return Required amount of `i` to send
+      */
   function get_dx(int128 i, int128 j, uint256 dy) external view returns (uint256);
packages/deploy/src/config.ts (1)

389-393: Consider adding type discriminators for adapter pairs

The GeneralAdapterPair and PendleMarketAdapterPair interfaces have identical structure except for the slippage property. Consider adding a type discriminator to safely distinguish between them at runtime.

 export interface GeneralAdapterPair {
+  type: 'general';
   tokenAId: string;
   tokenBId: string;
   poolAddress: string;
 }

 export interface PendleMarketAdapterPair {
+  type: 'pendleMarket';
   tokenAId: string;
   tokenBId: string;
   poolAddress: string;
   slippage: number;
 }

Also applies to: 404-409

packages/router/test/int/PendleCurveNgAdapterEthena.eth.spec.ts (1)

26-28: Consider extracting contract addresses to constants

Hardcoded contract addresses should be moved to a shared configuration file for better maintainability.

packages/router/test/int/PendleCurveRouterNgEthena.eth.spec.ts (5)

34-36: Consider using constants for slippage parameters.

The slippage parameters are defined using magic numbers without clear explanation of their calculation:

slippage: 20, // 20/100  = 20%
curveSlippage: 100, // 10/1000000 = 0.001%

Consider defining these as named constants with clear documentation of their calculation method and impact on swaps:

+ // Percentage values are represented as basis points (1 basis point = 0.01%)
+ const PENDLE_SLIPPAGE_BPS = 2000; // 20%
+ const CURVE_SLIPPAGE_BPS = 10;    // 0.001%

  const routeInput: PendleCurveRouterNgAdapter.RouteInputStruct = {
    pendleMarket: pendleMarket,
-   slippage: 20,
-   curveSlippage: 100,
+   slippage: PENDLE_SLIPPAGE_BPS,
+   curveSlippage: CURVE_SLIPPAGE_BPS,

37-49: Document the curve route structure.

The curve route configuration uses an array of addresses with unclear purpose and many zero addresses as padding.

Add detailed comments explaining:

  • The purpose of each address in the route
  • Why zero addresses are needed
  • The relationship between route addresses and swap parameters
    curveRoute: [
+     // Primary route: usd0++ -> usd0 -> usdc
      '0x4c9edd5852cd905f086c759e8383e09bff1e68b3',  // USDe token
      '0x02950460e2b9529d0e00284a5fa2d7bdf3fa4d72',  // Pool contract
      '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',  // USDC token
+     // Padding with zero addresses (required by Curve router interface)
      '0x0000000000000000000000000000000000000000',
      // ... remaining zero addresses
    ],

136-136: Remove commented out code.

The line contains both active and commented out code:

const minPtAmountOut = parseUnits('90', 18); //parseUnits('900', 18);

Remove the commented out value to improve code clarity.


188-191: Use consistent variable naming.

Variable names switch between different styles for the same concept:

const sUSDeBalanceBefore = await usdc.balanceOf(user.address);
console.log(
  `usdcBalanceBefore: ${formatUnits(sUSDeBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`
);

Standardize variable names across all test cases. For example:

- const sUSDeBalanceBefore = await usdc.balanceOf(user.address);
+ const usdcBalanceBefore = await usdc.balanceOf(user.address);
  console.log(
-   `usdcBalanceBefore: ${formatUnits(sUSDeBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`
+   `usdcBalanceBefore: ${formatUnits(usdcBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`
  );

Also applies to: 226-229


257-258: Use constants for time-related values.

The time manipulation uses magic numbers:

await ethers.provider.send('evm_increaseTime', [180 * 24 * 60 * 60]);

Define constants to make the time values more maintainable and clear:

+ // Time constants
+ const DAYS_IN_PERIOD = 180;
+ const SECONDS_PER_DAY = 24 * 60 * 60;
+ const MATURITY_PERIOD = DAYS_IN_PERIOD * SECONDS_PER_DAY;

- await ethers.provider.send('evm_increaseTime', [180 * 24 * 60 * 60]);
+ await ethers.provider.send('evm_increaseTime', [MATURITY_PERIOD]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee6f25d and a7b69fa.

📒 Files selected for processing (18)
  • packages/deploy/src/config.ts (2 hunks)
  • packages/deploy/src/deployer/MarginlyRouterDeployer.ts (4 hunks)
  • packages/deploy/src/deployer/configs.ts (8 hunks)
  • packages/deploy/src/index.ts (1 hunks)
  • packages/router/contracts/adapters/PendleCurveNgAdapter.sol (1 hunks)
  • packages/router/contracts/adapters/PendleCurveRouterNgAdapter.sol (1 hunks)
  • packages/router/contracts/adapters/interfaces/ICurvePool.sol (2 hunks)
  • packages/router/contracts/adapters/interfaces/ICurveRouterNg.sol (1 hunks)
  • packages/router/contracts/test/CurveEMATest/TestStableSwap2EMAOraclePool.sol (1 hunks)
  • packages/router/hardhat-configs/hardhat-eth-fork.config.ts (1 hunks)
  • packages/router/hardhat-configs/hardhat.config.ts (1 hunks)
  • packages/router/test/int/PendleCurveNgAdapterEBTC.eth.spec.ts (1 hunks)
  • packages/router/test/int/PendleCurveNgAdapterEthena.eth.spec.ts (1 hunks)
  • packages/router/test/int/PendleCurveNgAdapterWEETH.eth.spec.ts (1 hunks)
  • packages/router/test/int/PendleCurveRouterNgEthena.eth.spec.ts (1 hunks)
  • packages/router/test/int/PendleCurveRouterNgUSD0.eth.spec.ts (1 hunks)
  • packages/router/test/shared/tokens.ts (1 hunks)
  • packages/router/test/shared/utils.ts (3 hunks)
👮 Files not reviewed due to content moderation or server errors (3)
  • packages/router/test/int/PendleCurveNgAdapterEBTC.eth.spec.ts
  • packages/router/test/int/PendleCurveNgAdapterWEETH.eth.spec.ts
  • packages/router/test/int/PendleCurveRouterNgUSD0.eth.spec.ts
🧰 Additional context used
🪛 Gitleaks (8.21.2)
packages/router/test/int/PendleCurveNgAdapterEthena.eth.spec.ts

37-37: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


38-38: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (27)
packages/deploy/src/deployer/MarginlyRouterDeployer.ts (1)

32-32: Ensure Backward Compatibility with Method Signature Change

The addition of the optional curveRouter parameter in the deployMarginlyAdapter method may affect existing code that relies on the previous method signature. Please verify that all calls to this method throughout the codebase have been updated accordingly to include the new parameter where necessary.

Run the following script to find all usages of deployMarginlyAdapter and ensure they are updated:

✅ Verification successful

Optional Parameter Addition is Backward Compatible

The addition of the optional curveRouter parameter maintains backward compatibility as it's optional. The only existing usage in packages/deploy/src/index.ts remains valid without modification.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for all calls to 'deployMarginlyAdapter' and display the context.

rg 'deployMarginlyAdapter\(' -A 5

Length of output: 959

packages/router/contracts/adapters/PendleCurveNgAdapter.sol (3)

84-84: Security Review of SafeERC20.forceApprove Usage

The use of SafeERC20.forceApprove requires caution to prevent potential security risks such as race conditions or misbehaviors in token contracts. Ensure that all tokens interacted with are ERC20-compliant and that forceApprove is necessary over approve.

Confirm the necessity and safety of using forceApprove with the tokens involved:

✅ Verification successful

forceApprove Usage is Appropriate and Secure

The implementation follows security best practices:

  • Used only with trusted protocol contracts (DEX pools/routers)
  • Approvals are temporary and atomic
  • Proper cleanup (zero approvals) where necessary
  • Consistent with DEX integration requirements
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all instances of 'forceApprove' and review associated tokens.

rg 'SafeERC20\.forceApprove' -A 1

Length of output: 6235


129-178: ⚠️ Potential issue

Validate Swap Logic and Reentrancy Safety in swapCallback

The swapCallback function contains complex logic involving external calls and token transfers. Ensure that the function is safe from reentrancy attacks and that all state changes occur before external calls when necessary.

Review the function for potential reentrancy vulnerabilities:

✅ Verification successful

Reentrancy Protection Verified in swapCallback

The function follows the Checks-Effects-Interactions pattern with state changes occurring before external calls. Token approvals use forceApprove which safely resets allowances, and all external calls are properly sequenced with adequate validation checks.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for state changes before external calls in 'swapCallback'.

rg 'function swapCallback' -A 50

Length of output: 24342


61-66: Ensure Correct Initialization of Constants

Verify that the constants PENDLE_ONE, EPSILON, PENDLE_SLIPPAGE_ONE, MAX_ITERATIONS, and CURVE_SLIPPAGE_ONE are set to appropriate values that align with the system's precision and performance requirements.

Please confirm that these constants reflect the intended precision:

✅ Verification successful

Constants Verified as Correctly Initialized

The constants are properly initialized with appropriate values that align with protocol standards:

  • PENDLE_ONE = 1e18: Standard Ethereum precision
  • EPSILON = 1e15: Appropriate tolerance for numerical approximations (0.001 relative to PENDLE_ONE)
  • PENDLE_SLIPPAGE_ONE = 100: Standard percentage base unit (1% = 1)
  • MAX_ITERATIONS = 10: Sufficient for convergence in numerical methods
  • CURVE_SLIPPAGE_ONE = 1e6: Matches Curve protocol's precision
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Display the values of the defined constants.

rg 'constant .* =' -A 0 packages/router/contracts/adapters/PendleCurveNgAdapter.sol

Length of output: 329


Script:

#!/bin/bash
# Search for similar constant definitions in the codebase
echo "=== Searching for PENDLE_ONE usage ==="
rg "PENDLE_ONE" -B 2 -A 2

echo -e "\n=== Searching for EPSILON usage ==="
rg "EPSILON" -B 2 -A 2

echo -e "\n=== Searching for slippage constants ==="
rg "SLIPPAGE_ONE" -B 2 -A 2

echo -e "\n=== Searching for similar constants in other adapters ==="
fd "Adapter.sol$" --exec rg "constant .* =" -A 0 {}

Length of output: 21227

packages/router/contracts/adapters/PendleCurveRouterNgAdapter.sol (3)

523-593: Review Inversion Logic in _addPairs Function

When inverting the curveRoute, curveSwapParams, and curvePools, ensure that the inversion logic correctly handles all indexes and that no off-by-one errors exist.

Consider adding unit tests to validate the inversion logic.


173-183: ⚠️ Potential issue

Check Slippage Calculations for Accuracy and Safety

In slippage calculations involving estimatedQuoteAmount, ensure that arithmetic operations are correct to prevent unexpected results due to integer division and possible overflows.

Review the calculations and test with edge cases:

✅ Verification successful

Slippage Calculations are Safe and Accurate

The slippage implementation is mathematically sound with:

  • Proper validation ensuring slippage < 100%
  • Safe arithmetic operations preventing overflow
  • High precision (0.0001%) calculations
  • Correct order of operations maintaining precision
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Locate slippage calculation lines for manual review.

rg 'estimatedQuoteAmount.*=' -A 2

Length of output: 1394


Script:

#!/bin/bash
# Find CURVE_SLIPPAGE_ONE constant definition and any validation for curveSlippage
rg "CURVE_SLIPPAGE_ONE\s*=|require.*curveSlippage|if.*curveSlippage" -A 2

# Find struct definition for routeData to see curveSlippage type
ast-grep --pattern 'struct $_ {
  $$$
  curveSlippage $_
  $$$
}'

Length of output: 1427


95-97: ⚠️ Potential issue

Validate curveRouter Address Initialization

Ensure that the curveRouter address provided in the constructor is not only non-zero but also points to a valid and trusted Curve Router contract to prevent misrouting of assets.

Add an address validity check if possible, and verify the address:

packages/deploy/src/deployer/configs.ts (4)

171-176: Update to AdapterParam type includes new adapter interfaces

The addition of PendleCurveAdapterParam and PendleCurveRouterAdapterParam to the AdapterParam union type ensures that the new adapter configurations are appropriately handled throughout the codebase.


235-242: Added type guards for new adapter interfaces

The functions isPendleCurveRouterAdapter and isPendleCurveAdapter correctly implement type guards for the new adapter types, enhancing type safety when distinguishing between different adapter configurations.


247-247: Optional curveRouter property added to MarginlyConfigAdapter

The introduction of the optional curveRouter property allows the adapter configuration to include a Curve router address when necessary. Ensure that any logic utilizing MarginlyConfigAdapter properly accounts for the optional nature of this property to prevent potential undefined errors.


658-681: Ensure correctness of Pendle Curve Adapter configuration handling

The added handling for the PendleCurveAdapter in the adapter deployment logic seems appropriate. However, double-check that all required properties (ibTokenId, quoteTokenId, pendleMarket, slippage, curveSlippage, curvePool) are validated and parsed correctly to prevent runtime errors.

packages/router/hardhat-configs/hardhat-eth-fork.config.ts (1)

12-12: Confirm the updated blockNumber aligns with testing requirements

The blockNumber for Hardhat forking has been updated to 21493100. Ensure that this block number corresponds to the desired state of the Ethereum blockchain for your testing scenarios and that any dependencies required for the new adapters are present at this block.

packages/router/contracts/adapters/interfaces/ICurveRouterNg.sol (1)

1-1: Verify SPDX license identifier compatibility

The SPDX license identifier is set to GPL-2.0-or-later. Ensure that this license is compatible with the project's licensing and that inclusion of GPL-licensed code is intentional, as GPL licenses have strict copyleft requirements.

packages/router/test/shared/utils.ts (3)

23-25: LGTM! New Dex enum values follow consistent pattern.

The new Pendle-related Dex values (PendleMarket: 19, PendleCurveRouter: 20, PendleCurve: 21) maintain sequential numbering and clear naming.


41-44: LGTM! Gas usage tracking utility function.

The showGasUsage function provides a clean way to track gas consumption in tests.


46-51: LGTM! Efficient balance checking utility.

The showBalance function efficiently uses Promise.all for parallel execution of balance, symbol, and decimals retrieval.

packages/router/contracts/adapters/interfaces/ICurvePool.sol (2)

17-18: LGTM! Standard Curve interface method added.

The N_COINS() view function follows Curve's standard interface pattern.


25-25: LGTM! Correct view modifier added.

The coins() function is properly marked as view since it only reads state.

packages/router/contracts/test/CurveEMATest/TestStableSwap2EMAOraclePool.sol (1)

56-58: LGTM! Correct implementation of N_COINS.

The N_COINS function correctly returns 2, matching the two-token design of the pool.

packages/router/test/shared/tokens.ts (1)

20-22: Verify storage slots for mainnet tokens.

The storage slots for WETH, WBTC, and USDC should be verified against the mainnet contracts to ensure accuracy.

✅ Verification successful

Storage slots are properly documented with verification instructions

The storage slots in tokens.ts are test utilities with clear documentation on how to verify them:

  • File includes link to Solidity storage layout documentation
  • Instructions state to decompile contracts at ethervm.io to verify slots
  • Separate enums maintain slots for different networks (Arbitrum/Ethereum)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify storage slots by decompiling mainnet contracts
# Note: Using etherscan API to get contract bytecode for decompilation

# Get contract addresses from etherscan
echo "Verifying storage slots for WETH, WBTC, and USDC on Ethereum mainnet..."
for token in "WETH" "WBTC" "USDC"; do
  echo "Checking $token contract..."
  # Using etherscan API to get verified contract source
  curl -s "https://api.etherscan.io/api?module=contract&action=getsourcecode&address=${token}_ADDRESS"
done

Length of output: 942


Script:

#!/bin/bash
# Search for files containing references to storage slots and token configurations
echo "Searching for files with token slot references..."
rg -l "ERC20BalanceOfSlot|balanceOf.*slot|storage.*slot" --type ts

echo -e "\nSearching for documentation about slot calculation..."
fd README.md | xargs rg -l "slot|storage|ERC20"

echo -e "\nSearching for specific slot values..."
rg "0{48}[0-9]" --type ts

echo -e "\nSearching for test files related to token slots..."
fd "test.*token.*\.ts$|token.*test\.ts$"

Length of output: 2821


Script:

#!/bin/bash
# Get full content of tokens.ts to understand the context
echo "Getting full content of tokens.ts..."
cat packages/router/test/shared/tokens.ts

echo -e "\nSearching for any comments about storage slots..."
rg -C 5 "slot|storage" packages/router/test/shared/tokens.ts

Length of output: 3287

packages/router/hardhat-configs/hardhat.config.ts (1)

14-14: Compiler optimizations look good!

The changes enhance contract optimization:

  • Enabling IR-based codegen can produce more efficient bytecode
  • Increasing optimizer runs to 5M will generate more optimized code for frequently used contracts

Note: These changes will increase compilation time but are justified for complex adapter contracts.

Also applies to: 17-17

packages/deploy/src/config.ts (2)

382-387: Well-structured type hierarchy for adapter pairs!

The union type provides good type safety and extensibility for different adapter configurations.


411-429: Verify slippage ranges for Pendle Curve adapters

The PendleCurveAdapterPair and PendleCurveRouterAdapterPair interfaces include slippage parameters. Please ensure that these values are properly validated before use.

✅ Verification successful

Slippage validation confirmed in adapter implementations

The slippage parameters in both interfaces are properly validated:

  • All adapter contracts enforce slippage < 100% before use
  • Test files demonstrate typical values: 20-35% for Pendle slippage, 0.001-0.01% for Curve slippage
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for slippage validation in adapter contracts
rg -A 5 'slippage.*require|require.*slippage' 

Length of output: 47


Script:

#!/bin/bash
# Find files containing PendleCurveAdapterPair or PendleCurveRouterAdapterPair
echo "=== Files referencing the interfaces ==="
rg -l "PendleCurveAdapterPair|PendleCurveRouterAdapterPair"

echo -e "\n=== Searching for slippage validation patterns ==="
# Look for any validation patterns around slippage
rg -A 5 "slippage.*[<>=]|[<>=].*slippage"

echo -e "\n=== Looking for validation in constructors or init methods ==="
ast-grep --pattern 'constructor($$$) {
  $$$
  slippage
  $$$
}'

ast-grep --pattern 'function init($$$) {
  $$$
  slippage
  $$$
}'

Length of output: 15337

packages/router/test/int/PendleCurveNgAdapterEthena.eth.spec.ts (3)

95-115: Good test coverage for swap scenarios!

The tests thoroughly verify both exact input and output swaps in both directions.

Also applies to: 163-178


228-253: Well-structured post-maturity tests!

The tests properly verify that swaps are forbidden after maturity and check that balances remain unchanged.


32-39: High slippage value could mask issues

The test uses a 20% slippage value which is significantly high. Consider:

  1. Testing with more realistic slippage values
  2. Adding test cases that verify slippage protection works correctly
✅ Verification successful

High slippage values are appropriate for integration tests

The 20% slippage value is consistent with other integration tests in the codebase and appears to be intentional to ensure test reliability across different market conditions. While this value is higher than production settings (which use ~1%), it's appropriate for testing purposes to:

  • Prevent test failures due to price movements in test environments
  • Allow testing of the core functionality without interference from slippage constraints
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for slippage values in other test files for comparison
rg -A 2 'slippage:.*[0-9]+' 'test/**/*.ts'

Length of output: 97


Script:

#!/bin/bash
# Search for slippage-related code in the packages directory
rg -A 2 'slippage:.*[0-9]+' packages/
# Also search for slippage-related constants or configurations
rg -i "slippage" packages/ | grep -i "const\|default\|config"

Length of output: 16892

🧰 Tools
🪛 Gitleaks (8.21.2)

37-37: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


38-38: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

packages/deploy/src/index.ts (1)

222-223: LGTM: Curve router parameter added correctly

The addition of the curveRouter parameter aligns with the new adapter types introduced in the configuration.

Comment on lines +203 to +211
export interface PendleCurveRouterAdapterParam {
type: 'pendleCurveRouter';
pendleMarket: EthAddress;
slippage: number;
curveSlippage: number;
curveRoute: EthAddress[]; // array of fixed length 11
curveSwapParams: number[][]; // array of fixed length 5 x 5
curvePools: EthAddress[]; // array of fixed length 5
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enforce fixed array lengths using tuples in PendleCurveRouterAdapterParam

The properties curveRoute, curveSwapParams, and curvePools are expected to have fixed lengths (11, 5x5, and 5 respectively). Using standard arrays does not enforce these lengths at compile time. Consider using tuples to enforce fixed array lengths for better type safety and to catch length-related errors during development.

Apply this diff to change the array types to tuples:

 export interface PendleCurveRouterAdapterParam {
   type: 'pendleCurveRouter';
   pendleMarket: EthAddress;
   slippage: number;
   curveSlippage: number;
-  curveRoute: EthAddress[]; // array of fixed length 11
+  curveRoute: [
+    EthAddress, EthAddress, EthAddress, EthAddress, EthAddress, 
+    EthAddress, EthAddress, EthAddress, EthAddress, EthAddress, EthAddress
+  ]; // fixed length 11
-  curveSwapParams: number[][]; // array of fixed length 5 x 5
+  curveSwapParams: [
+    [number, number, number, number, number],
+    [number, number, number, number, number],
+    [number, number, number, number, number],
+    [number, number, number, number, number],
+    [number, number, number, number, number]
+  ]; // fixed size 5x5 matrix
-  curvePools: EthAddress[]; // array of fixed length 5
+  curvePools: [EthAddress, EthAddress, EthAddress, EthAddress, EthAddress]; // fixed length 5
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface PendleCurveRouterAdapterParam {
type: 'pendleCurveRouter';
pendleMarket: EthAddress;
slippage: number;
curveSlippage: number;
curveRoute: EthAddress[]; // array of fixed length 11
curveSwapParams: number[][]; // array of fixed length 5 x 5
curvePools: EthAddress[]; // array of fixed length 5
}
export interface PendleCurveRouterAdapterParam {
type: 'pendleCurveRouter';
pendleMarket: EthAddress;
slippage: number;
curveSlippage: number;
curveRoute: [
EthAddress, EthAddress, EthAddress, EthAddress, EthAddress,
EthAddress, EthAddress, EthAddress, EthAddress, EthAddress, EthAddress
]; // fixed length 11
curveSwapParams: [
[number, number, number, number, number],
[number, number, number, number, number],
[number, number, number, number, number],
[number, number, number, number, number],
[number, number, number, number, number]
]; // fixed size 5x5 matrix
curvePools: [EthAddress, EthAddress, EthAddress, EthAddress, EthAddress]; // fixed length 5
}

Comment on lines +52 to +54
function get_dx(int128 i, int128 j, uint256 dy) public view returns (uint256 dx) {
revert('Not implemented');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement get_dx function.

The get_dx function is currently not implemented and always reverts. Consider implementing it as the inverse of get_dy for complete test coverage.

Here's a suggested implementation:

   function get_dx(int128 i, int128 j, uint256 dy) public view returns (uint256 dx) {
-    revert('Not implemented');
+    if (i == j || i > 1 || j > 1) revert('wrong indexes');
+    if (i == 0) {
+      dx = Math.mulDiv(dy, price, PRICE_ONE);
+    } else {
+      dx = Math.mulDiv(dy, PRICE_ONE, price);
+    }
   }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 1 to 370
import { expect } from 'chai';
import { ethers } from 'hardhat';
import {
ERC20,
MarginlyRouter,
MarginlyRouter__factory,
PendleCurveRouterNgAdapter,
PendleCurveRouterNgAdapter__factory,
} from '../../typechain-types';
import { constructSwap, Dex, showGasUsage, SWAP_ONE } from '../shared/utils';
import { EthAddress } from '@marginly/common';
import { formatUnits, parseUnits } from 'ethers/lib/utils';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { EthereumMainnetERC20BalanceOfSlot, setTokenBalance } from '../shared/tokens';

async function initializeRouter(): Promise<{
ptToken: ERC20;
usdcToken: ERC20;
usdeToken: ERC20;
router: MarginlyRouter;
pendleCurveAdapter: PendleCurveRouterNgAdapter;
owner: SignerWithAddress;
user: SignerWithAddress;
}> {
const [owner, user] = await ethers.getSigners();
const ptToken = await ethers.getContractAt('ERC20', '0x8a47b431a7d947c6a3ed6e42d501803615a97eaa');
const usdcToken = await ethers.getContractAt('ERC20', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48');
const usdeToken = await ethers.getContractAt('ERC20', '0x4c9edd5852cd905f086c759e8383e09bff1e68b3');
const pendleMarket = '0xb451a36c8b6b2eac77ad0737ba732818143a0e25';
const curveRouterAddress = '0x16c6521dff6bab339122a0fe25a9116693265353';

// Route to make swap pt-USDe -> usde -> usdc
const routeInput: PendleCurveRouterNgAdapter.RouteInputStruct = {
pendleMarket: pendleMarket,
slippage: 20, // 20/100 = 20%
curveSlippage: 100, // 10/1000000 = 0.001%
curveRoute: [
'0x4c9edd5852cd905f086c759e8383e09bff1e68b3',
'0x02950460e2b9529d0e00284a5fa2d7bdf3fa4d72',
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
], // curve route usd0++ -> usd0 -> usdc
curveSwapParams: [
[0, 1, 1, 1, 2],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
],
curvePools: [
'0x02950460e2b9529d0e00284a5fa2d7bdf3fa4d72',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000',
],
};
const pendleCurveAdapter = await new PendleCurveRouterNgAdapter__factory()
.connect(owner)
.deploy(curveRouterAddress, [routeInput]);

const routerInput = {
dexIndex: Dex.PendleCurveRouter,
adapter: pendleCurveAdapter.address,
};
const router = await new MarginlyRouter__factory().connect(owner).deploy([routerInput]);

await setTokenBalance(
usdcToken.address,
EthereumMainnetERC20BalanceOfSlot.USDC,
EthAddress.parse(user.address),
parseUnits('1000', 6)
);
await setTokenBalance(
ptToken.address,
EthereumMainnetERC20BalanceOfSlot.PTSUSDE,
EthAddress.parse(user.address),
parseUnits('1000', 18)
);

return {
ptToken,
usdcToken,
usdeToken,
router,
pendleCurveAdapter,
owner,
user,
};
}

// Tests for running in ethereum mainnet fork
describe('PendleCurveRouter PT-usde - usdc', () => {
describe('Pendle swap pre maturity', () => {
let ptToken: ERC20;
let usdc: ERC20;
let usde: ERC20;
let router: MarginlyRouter;
let pendleCurveAdapter: PendleCurveRouterNgAdapter;
let user: SignerWithAddress;
let owner: SignerWithAddress;

beforeEach(async () => {
({
ptToken,
usdcToken: usdc,
usdeToken: usde,
router,
pendleCurveAdapter,
owner,
user,
} = await initializeRouter());
});

it('USDC to pt-USDe exact input', async () => {
const ptBalanceBefore = await ptToken.balanceOf(user.address);
console.log(
`pt-usde balance Before: ${formatUnits(ptBalanceBefore, await ptToken.decimals())} ${await ptToken.symbol()}`
);
const usdcBalanceBefore = await usdc.balanceOf(user.address);
console.log(
`USDC balance before: ${formatUnits(usdcBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`
);

const swapCalldata = constructSwap([Dex.PendleCurveRouter], [SWAP_ONE]);
const usdcSwapAmount = parseUnits('100', 6);
await usdc.connect(user).approve(router.address, usdcSwapAmount);

const minPtAmountOut = parseUnits('90', 18); //parseUnits('900', 18);

const tx = await router
.connect(user)
.swapExactInput(swapCalldata, usdc.address, ptToken.address, usdcSwapAmount, minPtAmountOut);
await showGasUsage(tx);

const ptBalanceAfter = await ptToken.balanceOf(user.address);
console.log(`ptBalanceAfter: ${formatUnits(ptBalanceAfter, await ptToken.decimals())} ${await ptToken.symbol()}`);
expect(ptBalanceAfter).to.be.greaterThan(ptBalanceBefore);
const usdcBalanceAfter = await usdc.balanceOf(user.address);
console.log(`usdcBalanceAfter: ${formatUnits(usdcBalanceAfter, await usdc.decimals())} ${await usdc.symbol()}`);
expect(usdcBalanceBefore.sub(usdcBalanceAfter)).to.be.lessThanOrEqual(usdcSwapAmount);
});

it('USDC to pt-USDe exact output', async () => {
const ptBalanceBefore = await ptToken.balanceOf(user.address);
console.log(
`pt-USDe balance Before: ${formatUnits(ptBalanceBefore, await ptToken.decimals())} ${await ptToken.symbol()}`
);
const usdcBalanceBefore = await usdc.balanceOf(user.address);
console.log(
`USDC balance before: ${formatUnits(usdcBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`
);
const swapCalldata = constructSwap([Dex.PendleCurveRouter], [SWAP_ONE]);

const exactPtOut = parseUnits('500', 18);
const usdcMaxIn = parseUnits('600', 6);
await usdc.connect(user).approve(router.address, usdcMaxIn);
const tx = await router
.connect(user)
.swapExactOutput(swapCalldata, usdc.address, ptToken.address, usdcMaxIn, exactPtOut);
await showGasUsage(tx);

const ptBalanceAfter = await ptToken.balanceOf(user.address);
console.log(`ptBalanceAfter: ${formatUnits(ptBalanceAfter, await ptToken.decimals())} ${await ptToken.symbol()}`);
expect(ptBalanceAfter.sub(ptBalanceBefore)).to.be.eq(exactPtOut);
const usdcBalanceAfter = await usdc.balanceOf(user.address);
console.log(`usdcBalanceAfter: ${formatUnits(usdcBalanceAfter, await usdc.decimals())} ${await usdc.symbol()}`);
expect(usdcBalanceBefore).to.be.greaterThan(usdcBalanceAfter);

const usd0PlusPlusOnAdapter = await usde.balanceOf(pendleCurveAdapter.address);
console.log(
`usde stays on adapter: ${formatUnits(usd0PlusPlusOnAdapter, await usde.decimals())} ${await usde.symbol()}`
);
});

it('pt-USDe to USDC exact input', async () => {
const ptBalanceBefore = await ptToken.balanceOf(user.address);
console.log(
`ptBalanceBefore: ${formatUnits(ptBalanceBefore, await ptToken.decimals())} ${await ptToken.symbol()}`
);
const sUSDeBalanceBefore = await usdc.balanceOf(user.address);
console.log(
`usdcBalanceBefore: ${formatUnits(sUSDeBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`
);
const swapCalldata = constructSwap([Dex.PendleCurveRouter], [SWAP_ONE]);
const ptIn = ptBalanceBefore;
await ptToken.connect(user).approve(router.address, ptIn);
const tx = await router.connect(user).swapExactInput(swapCalldata, ptToken.address, usdc.address, ptIn, 0);
await showGasUsage(tx);

const ptBalanceAfter = await ptToken.balanceOf(user.address);
console.log(`ptBalanceAfter: ${formatUnits(ptBalanceAfter, await ptToken.decimals())} ${await ptToken.symbol()}`);
expect(ptBalanceBefore.sub(ptBalanceAfter)).to.be.eq(ptIn);
const sUsdeBalanceAfter = await usdc.balanceOf(user.address);
console.log(`usdcBalanceAfter: ${formatUnits(sUsdeBalanceAfter, await usdc.decimals())} ${await usdc.symbol()}`);
expect(sUsdeBalanceAfter).to.be.greaterThan(sUSDeBalanceBefore);
});

it('pt-USDe to USDC exact output', async () => {
const ptBalanceBefore = await ptToken.balanceOf(user.address);
console.log(
`ptBalanceBefore: ${formatUnits(ptBalanceBefore, await ptToken.decimals())} ${await ptToken.symbol()}`
);
const usdcBalanceBefore = await usdc.balanceOf(user.address);
console.log(`usdcBalanceBefore: ${formatUnits(usdcBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`);
const swapCalldata = constructSwap([Dex.PendleCurveRouter], [SWAP_ONE]);
const usdcOut = parseUnits('500', 6);
const maxPtIn = parseUnits('600', 18);
await ptToken.connect(user).approve(router.address, maxPtIn);
const tx = await router
.connect(user)
.swapExactOutput(swapCalldata, ptToken.address, usdc.address, maxPtIn, usdcOut);
await showGasUsage(tx);

const ptBalanceAfter = await ptToken.balanceOf(user.address);
console.log(`ptBalanceAfter: ${formatUnits(ptBalanceAfter, await ptToken.decimals())} ${await ptToken.symbol()}`);
expect(ptBalanceBefore).to.be.greaterThan(ptBalanceAfter);
const usdcBalanceAfter = await usdc.balanceOf(user.address);
console.log(`usdcBalanceAfter: ${formatUnits(usdcBalanceAfter, await usdc.decimals())} ${await usdc.symbol()}`);
expect(usdcBalanceAfter.sub(usdcBalanceBefore)).to.be.eq(usdcOut);

const usdcBalanceOnAdapter = await usdc.balanceOf(pendleCurveAdapter.address);
console.log(
`usdcBalanceOnAdapter: ${formatUnits(usdcBalanceOnAdapter, await usdc.decimals())} ${await usdc.symbol()}`
);
});
});

describe('Pendle swap post maturity', () => {
let ptToken: ERC20;
let usdc: ERC20;
let usde: ERC20;
let router: MarginlyRouter;
let pendleCurveAdapter: PendleCurveRouterNgAdapter;
let user: SignerWithAddress;
let owner: SignerWithAddress;

beforeEach(async () => {
({
ptToken,
usdcToken: usdc,
usdeToken: usde,
router,
pendleCurveAdapter,
owner,
user,
} = await initializeRouter());

// move time and make after maturity
await ethers.provider.send('evm_increaseTime', [180 * 24 * 60 * 60]);
await ethers.provider.send('evm_mine', []);
});

it('USDC to pt-USDe exact input, forbidden', async () => {
const ptBalanceBefore = await ptToken.balanceOf(user.address);
console.log(
`ptBalanceBefore: ${formatUnits(ptBalanceBefore, await ptToken.decimals())} ${await ptToken.symbol()}`
);
const sUsdeBalanceBefore = await usdc.balanceOf(user.address);
console.log(
`usdcBalanceBefore: ${formatUnits(sUsdeBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`
);

const swapCalldata = constructSwap([Dex.PendleCurveRouter], [SWAP_ONE]);
await usdc.connect(user).approve(router.address, sUsdeBalanceBefore);
const tx = router
.connect(user)
.swapExactInput(
swapCalldata,
usdc.address,
ptToken.address,
sUsdeBalanceBefore,
sUsdeBalanceBefore.mul(9).div(10)
);

await expect(tx).to.be.revertedWithCustomError(pendleCurveAdapter, 'NotSupported');

console.log('This swap is forbidden after maturity');
const ptBalanceAfter = await ptToken.balanceOf(user.address);
console.log(`ptBalanceAfter: ${formatUnits(ptBalanceAfter, await ptToken.decimals())} ${await ptToken.symbol()}`);
expect(ptBalanceAfter).to.be.eq(ptBalanceBefore);
const sUsdeBalanceAfter = await usdc.balanceOf(user.address);
console.log(`usdcBalanceAfter: ${formatUnits(sUsdeBalanceAfter, await usdc.decimals())} ${await usdc.symbol()}`);
expect(sUsdeBalanceAfter).to.be.eq(sUsdeBalanceBefore);
});

it('USDC to pt-USDe exact output, forbidden', async () => {
const ptBalanceBefore = await ptToken.balanceOf(user.address);
console.log(
`ptBalanceBefore: ${formatUnits(ptBalanceBefore, await ptToken.decimals())} ${await ptToken.symbol()}`
);
const usdcBalanceBefore = await usdc.balanceOf(user.address);
console.log(
`sUsdeBalanceBefore: ${formatUnits(usdcBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`
);

const swapCalldata = constructSwap([Dex.PendleCurveRouter], [SWAP_ONE]);
const ptOut = usdcBalanceBefore.div(2);
await usdc.connect(user).approve(router.address, usdcBalanceBefore);
const tx = router
.connect(user)
.swapExactOutput(swapCalldata, usdc.address, ptToken.address, usdcBalanceBefore, ptOut);
await expect(tx).to.be.revertedWithCustomError(pendleCurveAdapter, 'NotSupported');

console.log('This swap is forbidden after maturity');
const ptBalanceAfter = await ptToken.balanceOf(user.address);
console.log(`ptBalanceAfter: ${formatUnits(ptBalanceAfter, await ptToken.decimals())} ${await ptToken.symbol()}`);
expect(ptBalanceAfter).to.be.eq(ptBalanceBefore);
const usdcBalanceAfter = await usdc.balanceOf(user.address);
console.log(`usdcBalanceAfter: ${formatUnits(usdcBalanceAfter, await usdc.decimals())} ${await usdc.symbol()}`);
expect(usdcBalanceAfter).to.be.eq(usdcBalanceBefore);
});

it('pt-USDe to USDC exact input', async () => {
const ptBalanceBefore = await ptToken.balanceOf(user.address);
console.log(
`ptBalanceBefore: ${formatUnits(ptBalanceBefore, await ptToken.decimals())} ${await ptToken.symbol()}`
);
const usdcBalanceBefore = await usdc.balanceOf(user.address);
console.log(`usdcBalanceBefore: ${formatUnits(usdcBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`);

const swapCalldata = constructSwap([Dex.PendleCurveRouter], [SWAP_ONE]);
const ptIn = ptBalanceBefore;
await ptToken.connect(user).approve(router.address, ptIn);
const tx = await router.connect(user).swapExactInput(swapCalldata, ptToken.address, usdc.address, ptIn, 0);
await showGasUsage(tx);

const ptBalanceAfter = await ptToken.balanceOf(user.address);
console.log(`ptBalanceAfter: ${formatUnits(ptBalanceAfter, await ptToken.decimals())} ${await ptToken.symbol()}`);
expect(ptBalanceBefore.sub(ptBalanceAfter)).to.be.eq(ptIn);
const usdcBalanceAfter = await usdc.balanceOf(user.address);
console.log(`usdcBalanceAfter: ${formatUnits(usdcBalanceAfter, await usdc.decimals())} ${await usdc.symbol()}`);
expect(usdcBalanceAfter).to.be.greaterThan(usdcBalanceBefore);
});

it('pt-USDe to USDC exact output', async () => {
const ptBalanceBefore = await ptToken.balanceOf(user.address);
console.log(
`ptBalanceBefore: ${formatUnits(ptBalanceBefore, await ptToken.decimals())} ${await ptToken.symbol()}`
);
const sUsdeBalanceBefore = await usdc.balanceOf(user.address);
console.log(
`sUsdeBalanceBefore: ${formatUnits(sUsdeBalanceBefore, await usdc.decimals())} ${await usdc.symbol()}`
);

const swapCalldata = constructSwap([Dex.PendleCurveRouter], [SWAP_ONE]);
const usdcOut = parseUnits('900', 6);
await ptToken.connect(user).approve(router.address, ptBalanceBefore);
const maxPtIn = parseUnits('1000', 18);
const tx = await router
.connect(user)
.swapExactOutput(swapCalldata, ptToken.address, usdc.address, maxPtIn, usdcOut);
await showGasUsage(tx);

const ptBalanceAfter = await ptToken.balanceOf(user.address);
console.log(`ptBalanceAfter: ${formatUnits(ptBalanceAfter, await ptToken.decimals())} ${await ptToken.symbol()}`);
expect(ptBalanceBefore).to.be.greaterThan(ptBalanceAfter);
const usdcBalanceAfter = await usdc.balanceOf(user.address);
console.log(`sUsdeBalanceAfter: ${formatUnits(usdcBalanceAfter, await usdc.decimals())} ${await usdc.symbol()}`);
expect(usdcBalanceAfter.sub(sUsdeBalanceBefore)).to.be.eq(usdcOut);
});
});
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider extracting common test utilities and helpers.

The test files contain significant duplication in setup, time manipulation, and balance checking logic. This pattern is repeated across all Pendle adapter test files.

Consider:

  1. Creating a shared test fixture for Pendle adapter tests
  2. Extracting common balance checking logic into helper functions
  3. Creating a shared configuration for token addresses and parameters

Example helper function:

async function checkBalanceChange(
  token: ERC20,
  address: string,
  beforeBalance: BigNumber,
  expectedChange: BigNumber,
  changeType: 'increase' | 'decrease' | 'exact'
): Promise<void> {
  const afterBalance = await token.balanceOf(address);
  const symbol = await token.symbol();
  const decimals = await token.decimals();
  
  console.log(`${symbol} balance change: ${formatUnits(afterBalance.sub(beforeBalance), decimals)} ${symbol}`);
  
  switch (changeType) {
    case 'increase':
      expect(afterBalance).to.be.greaterThan(beforeBalance);
      break;
    case 'decrease':
      expect(beforeBalance).to.be.greaterThan(afterBalance);
      break;
    case 'exact':
      expect(afterBalance.sub(beforeBalance)).to.be.eq(expectedChange);
      break;
  }
}

/// @dev index of coin in curve pool
uint8 i;
/// @dev index of coin in curve pool
uint8 j;
Copy link
Contributor

@optifat optifat Jan 10, 2025

Choose a reason for hiding this comment

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

Those names definitely must be replaced with something like ibTokenCurveIndex, quoteTokenCurveIndex

@rudewalt rudewalt merged commit 1c1b1fa into feature/marginly-pendle Jan 14, 2025
3 checks passed
@rudewalt rudewalt deleted the feature/pendle-curve-router branch January 14, 2025 07:30
@coderabbitai coderabbitai bot mentioned this pull request Jan 27, 2025
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.

3 participants