-
Notifications
You must be signed in to change notification settings - Fork 11
pendleCurve adapter #203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pendleCurve adapter #203
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughThis 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: Changes
Sequence DiagramsequenceDiagram
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
Possibly related PRs
Poem
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this 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 MissingcurveRouterParameter More GracefullyCurrently, the code throws a generic
ErrorwhencurveRouteris not provided for aPendleCurveRouterAdapter. 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_addPairsFunctionIn the
_addPairsfunction, consider usingforloops with proper bounds checking and utilizinguncheckedblocks 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
uncheckedis justified and documented.packages/deploy/src/deployer/configs.ts (1)
682-717: Refactor to eliminate redundant runtime checks with type enforcementWith the suggested use of tuples to enforce array lengths at the type level, the runtime checks for the lengths of
curveRoute,curvePools, andcurveSwapParamsin thePendleCurveRouteradapter 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 usingcalldataIn the
ICurveRouterNginterface, the functionsexchange,get_dy, andget_dxusememoryfor array parameters. Since these functions are external, usingcalldatainstead ofmemoryfor array parameters can be more gas-efficient and avoids unnecessary data copying.Apply this diff to change
memorytocalldata: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 pairsThe
GeneralAdapterPairandPendleMarketAdapterPairinterfaces have identical structure except for theslippageproperty. 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 constantsHardcoded 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
📒 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 ChangeThe addition of the optional
curveRouterparameter in thedeployMarginlyAdaptermethod 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
deployMarginlyAdapterand ensure they are updated:✅ Verification successful
Optional Parameter Addition is Backward Compatible
The addition of the optional
curveRouterparameter maintains backward compatibility as it's optional. The only existing usage inpackages/deploy/src/index.tsremains 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 5Length of output: 959
packages/router/contracts/adapters/PendleCurveNgAdapter.sol (3)
84-84: Security Review ofSafeERC20.forceApproveUsageThe use of
SafeERC20.forceApproverequires 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 thatforceApproveis necessary overapprove.Confirm the necessity and safety of using
forceApprovewith the tokens involved:✅ Verification successful
forceApproveUsage is Appropriate and SecureThe 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 1Length of output: 6235
129-178:⚠️ Potential issueValidate Swap Logic and Reentrancy Safety in
swapCallbackThe
swapCallbackfunction 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
swapCallbackThe function follows the Checks-Effects-Interactions pattern with state changes occurring before external calls. Token approvals use
forceApprovewhich 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 50Length of output: 24342
61-66: Ensure Correct Initialization of ConstantsVerify that the constants
PENDLE_ONE,EPSILON,PENDLE_SLIPPAGE_ONE,MAX_ITERATIONS, andCURVE_SLIPPAGE_ONEare 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 precisionEPSILON = 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 methodsCURVE_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.solLength 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_addPairsFunctionWhen inverting the
curveRoute,curveSwapParams, andcurvePools, 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 issueCheck 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 2Length 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 issueValidate
curveRouterAddress InitializationEnsure that the
curveRouteraddress 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 toAdapterParamtype includes new adapter interfacesThe addition of
PendleCurveAdapterParamandPendleCurveRouterAdapterParamto theAdapterParamunion type ensures that the new adapter configurations are appropriately handled throughout the codebase.
235-242: Added type guards for new adapter interfacesThe functions
isPendleCurveRouterAdapterandisPendleCurveAdaptercorrectly implement type guards for the new adapter types, enhancing type safety when distinguishing between different adapter configurations.
247-247: OptionalcurveRouterproperty added toMarginlyConfigAdapterThe introduction of the optional
curveRouterproperty allows the adapter configuration to include a Curve router address when necessary. Ensure that any logic utilizingMarginlyConfigAdapterproperly accounts for the optional nature of this property to prevent potentialundefinederrors.
658-681: Ensure correctness of Pendle Curve Adapter configuration handlingThe added handling for the
PendleCurveAdapterin 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 updatedblockNumberaligns with testing requirementsThe
blockNumberfor Hardhat forking has been updated to21493100. 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 compatibilityThe 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.tsare 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" doneLength 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.tsLength 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 adaptersThe
PendleCurveAdapterPairandPendleCurveRouterAdapterPairinterfaces 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 issuesThe test uses a 20% slippage value which is significantly high. Consider:
- Testing with more realistic slippage values
- 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 correctlyThe addition of the curveRouter parameter aligns with the new adapter types introduced in the configuration.
| 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 | ||
| } |
There was a problem hiding this comment.
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.
| 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 | |
| } |
| function get_dx(int128 i, int128 j, uint256 dy) public view returns (uint256 dx) { | ||
| revert('Not implemented'); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
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:
- Creating a shared test fixture for Pendle adapter tests
- Extracting common balance checking logic into helper functions
- 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; |
There was a problem hiding this comment.
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
Summary by CodeRabbit
Based on the comprehensive changes, here are the release notes:
New Features
Contracts
PendleCurveNgAdapterandPendleCurveRouterNgAdapterTesting
Configuration