-
Notifications
You must be signed in to change notification settings - Fork 7
Feat: Pendle PT Helper #102
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
Merged
Merged
Changes from 16 commits
Commits
Show all changes
67 commits
Select commit
Hold shift + click to select a range
b8e6526
add CurveDolaLPHelperDynamic
0xtj24 4964e65
add DolasUSDe tests
0xtj24 93b65e5
simplify _addLiquidity for dynamic array
0xtj24 db08147
add DolasUSDs LP tests, add initial ALE test refactor
0xtj24 f5998d1
add DolascrvUSD LP tests
0xtj24 c218c88
update BorrowController test setup
0xtj24 07ad3f5
add sDOLA-scrvUSD markets tests
0xtj24 9c6ce4f
add ALE sDOLA LP helper
0xtj24 2f41fa5
add ALE tests for sDOLA-scrvUSD markets
0xtj24 ef665eb
update config file
0xtj24 abfaf46
use interface
0xtj24 16114e5
update natspec
0xtj24 cc2a55d
add YearnV2 deployed vault sDola-scrvUSD test, add fuzz tests
0xtj24 d671a3e
add initial PendlePTHelper and fork test
0xtj24 e6fed9d
Merge branch 'dev' into feat/pendle-pt-helper
0xtj24 4d6e58c
rm unused import
0xtj24 12c5327
update natspec
0xtj24 1646d74
update natspec
0xtj24 712fa6d
more natspec update
0xtj24 af6107c
fix typos
0xtj24 20e7b77
add safety check for stucked Pendle tokens
0xtj24 2cf4856
add Pendle PT helper fuzz tests
0xtj24 f646dce
rm unused import in test
0xtj24 419181f
add initial PendlePTHelper update, add ALEPendle, update tests
0xtj24 6a9df68
use selector check for PendlePTHelper, add tests
0xtj24 2f084d6
update natspec
0xtj24 f140324
store maturity for gas optimization
0xtj24 d1f67f9
add Events
0xtj24 b858075
update natspec
0xtj24 713b3e4
apply review feedback
0xtj24 e8611f5
add refundExcess internal function
0xtj24 1d87b03
add minOut check for buy and sell DBR
0xtj24 3cc6e03
revert if helper or buySellToken not set
0xtj24 b05b6f4
fix updateMarketHelper logic, add test
0xtj24 1c04da4
use BytesLib library in PendlePTHelper
0xtj24 fb836b0
add Dola-DeUSD market and ALE tests
0xtj24 ce13367
fix old test
0xtj24 7650d9f
add Feed and Escrow tests, update market test with CL feed
0xtj24 89bf3fb
fix typo
0xtj24 5dd5b66
rm spender from ALEPendle, update tests
0xtj24 3945ec0
introduce ALEV2, update ERC4626Helper and YVYCRVHelper, update tests
0xtj24 6c05ba2
update CurveDolaLPHelper and tests
0xtj24 5e7c94c
update CurveDolaLPHelperDynamic and tests
0xtj24 36180d4
Merge branch 'feat/sDOLA-scrvUSD' into feat/pendle-pt-helper
0xtj24 37268a7
update CurveSDolaLPHelperDynamic and tests
0xtj24 c085b9d
add USDe before maturity with NAV, add tests with NAV
0xtj24 d90491a
add maturity check and test
0xtj24 8c2c4ae
update natspec
0xtj24 49f8efb
add multiple proxy support for ALEV2, update tests
0xtj24 5625279
Merge branch 'dev' into feat/dola-deUsd
08xmt 7a13fb8
Merge branch 'dev' into feat/pt-sude
08xmt aa97bd9
use PendleNAVFeed returning block.timestamp
0xtj24 8690cf0
Merge branch 'feat/pt-sude' of github.com-work_user2:InverseFinance/F…
0xtj24 c80d052
fix sdeUSD test
0xtj24 84c1f35
add test for staleness in Market tests
0xtj24 4640e8c
update staleness test for Markets
0xtj24 3c8163d
use deployed contracts for PendlePTsUSDe29May25
0xtj24 272e9c6
add initial ALEV2 and helpers addresses in tests
0xtj24 8a97b69
Merge branch 'feat/pt-sude' into feat/pendle-pt-helper
0xtj24 7ec0bcc
use latest block for SDola-scrvUSD tests, update ALEPendlePT tests
0xtj24 5c7d7ce
uncomment assertions
0xtj24 4110616
add ALE PT USDe 29May25 test
0xtj24 5455f32
Feat: Dola-USR markets (#112)
0xtj24 0e7c2d7
Merge branch 'feat/dola-deUsd' into feat/pendle-pt-helper
0xtj24 a18c1f6
update Dola-DeUSD and Dola-USR ALE tests
0xtj24 6b4f16c
update setMarket function
0xtj24 ebc76db
update tests with latest deployments
0xtj24 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,281 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| import {IMarket} from "src/interfaces/IMarket.sol"; | ||
| import {Sweepable, SafeERC20, IERC20} from "src/util/Sweepable.sol"; | ||
| import {IMultiMarketTransformHelper} from "src/interfaces/IMultiMarketTransformHelper.sol"; | ||
|
|
||
| /** | ||
| * @title Pendle PT ALE and market helper | ||
| * @notice This contract is a generalized ALE and market helper contract for Pendle PT tokens from and to DOLA. | ||
| * @dev Carefully prepare the router calldata from Pendle API when using it from the ALE: | ||
| * When converting TO collateral, the receiver in Pendle API has to be set to this contract address | ||
| * When converting FROM collateral, the receiver in Pendle API has to be set to the ALE address. | ||
| * The Pendle Router can either SWAP DOLA for PT or MINT PT and YT as well SWAP PT for DOLA or REDEEM PT (using YT before maturity) for DOLA. | ||
| * Do not use this contract for other routes otherwise won't work properly. | ||
| **/ | ||
|
|
||
| contract PendlePTHelper is Sweepable, IMultiMarketTransformHelper { | ||
| using SafeERC20 for IERC20; | ||
|
|
||
| error InsufficientDOLA(); | ||
| error InsufficientPT(); | ||
| error InsufficientYT(); | ||
| error MarketNotSet(address market); | ||
| error PendleSwapFailed(); | ||
|
|
||
| struct PT { | ||
| address pt; | ||
| address yt; | ||
| } | ||
|
|
||
| event MarketSet( | ||
| address indexed market, | ||
| address indexed pt, | ||
| address indexed yt | ||
| ); | ||
| event MarketRemoved(address indexed market); | ||
|
|
||
| IERC20 public immutable DOLA; | ||
| address public immutable router; | ||
|
|
||
| /// @notice Mapping of market addresses to their associated PT and YT tokens. | ||
| mapping(address => PT) public markets; | ||
|
|
||
| /** @dev Constructor | ||
| @param _gov The address of Inverse Finance governance | ||
| @param _guardian The address of the guardian | ||
| @param _pendleRouter The address of the Pendle Router | ||
| **/ | ||
| constructor( | ||
| address _gov, | ||
| address _guardian, | ||
| address _dola, | ||
| address _pendleRouter | ||
| ) Sweepable(_gov, _guardian) { | ||
| DOLA = IERC20(_dola); | ||
| router = _pendleRouter; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Convert DOLA to PT or PT and YT | ||
| * @dev Used by the ALE but can be called by anyone. Carefully review input data for Pendle API. | ||
| * The receiver in Pendle API has to be set to this contract address. If a MINT is performed, provide a ytRecipient or YT will be kept in this contract. | ||
| * @param amount The amount of underlying token to be deposited. | ||
| * @param data Encoded address of the market, minimum amount of PT to receive (and possibly YT), ytRecipient if minting, and Pendle callData. | ||
| * @return collateralAmount The amount of PT (and possibly YT) token received. | ||
| */ | ||
| function transformToCollateral( | ||
| uint256 amount, | ||
| bytes calldata data | ||
| ) external override returns (uint256 collateralAmount) { | ||
| collateralAmount = transformToCollateral(amount, msg.sender, data); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Convert DOLA to PT or PT and YT | ||
| * @dev The receiver in Pendle API has to be set to this contract address. If a MINT is performed, provide a ytRecipient or YT will be kept in this contract. | ||
| * @param amount The amount of DOLA to be deposited. | ||
| * @param recipient The recipient address of PT token. | ||
| * @param data Encoded address of the market, minimum amount of PT to receive (and possibly YT), ytRecipient if minting, and Pendle callData. | ||
| * @return collateralAmount The amount of PT (and possibly YT) token received. | ||
| */ | ||
| function transformToCollateral( | ||
| uint256 amount, | ||
| address recipient, | ||
| bytes calldata data | ||
| ) public override returns (uint256 collateralAmount) { | ||
| ( | ||
| address market, | ||
| uint256 minMint, // Minimum amount of PT to receive (and possibly YT) | ||
| address ytRecipient, | ||
| bytes memory callData | ||
| ) = abi.decode(data, (address, uint256, address, bytes)); | ||
|
|
||
| _revertIfMarketNotSet(market); | ||
|
|
||
| IERC20 pt = IERC20(markets[market].pt); | ||
| IERC20 yt = IERC20(markets[market].yt); | ||
| DOLA.safeTransferFrom(msg.sender, address(this), amount); | ||
| DOLA.approve(router, amount); | ||
| // Avoid accounting for possibly stucked token for previous bad input and allow recovery | ||
| uint256 ptBalBefore = pt.balanceOf(address(this)); | ||
| uint256 ytBalBefore = yt.balanceOf(address(this)); | ||
| (bool success, ) = router.call(callData); | ||
| if (!success) revert PendleSwapFailed(); | ||
|
|
||
| uint256 ptBal = pt.balanceOf(address(this)) - ptBalBefore; | ||
|
|
||
| if (ptBal < minMint) revert InsufficientPT(); | ||
| if (recipient != address(this)) pt.safeTransfer(recipient, ptBal); | ||
| // Send YT to user if specified | ||
| if (ytRecipient != address(0)) { | ||
| uint256 ytBalMinted = yt.balanceOf(address(this)) - ytBalBefore; | ||
| if (ytBalMinted < minMint) revert InsufficientYT(); | ||
| yt.safeTransfer(ytRecipient, ytBalMinted); | ||
| } | ||
|
|
||
| return ptBal; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Redeems PT token for DOLA. | ||
| * @dev Used by the ALE but can be called by anyone. Carefully review input data for Pendle API. | ||
| * The receiver in Pendle API has to be set same as the recipient. If a REDEEM is performed, include a ytProvider with enough allowance. | ||
| * @param amount The amount of PT token to be redeemed (and YT if specified). | ||
| * @param data Encoded address of the market, minimum amount of DOLA to receive, ytProvider, and Pendle callData. | ||
| * @return dolaAmount The amount of DOLA redeemed. | ||
| */ | ||
| function transformFromCollateral( | ||
| uint256 amount, | ||
| bytes calldata data | ||
| ) external override returns (uint256 dolaAmount) { | ||
| dolaAmount = transformFromCollateral(amount, msg.sender, data); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Redeems Collateral for DOLA. | ||
| * @dev The receiver in Pendle API has to be set same as the recipient. If a REDEEM is performed, include a ytProvider with enough allowance. | ||
| * @param amount The amount of PT Token to be redeemed (and YT if specified). | ||
| * @param recipient The address to which the underlying token is transferred. | ||
| * @param data Encoded address of the market, minimum amount of DOLA to receive for the recipient, ytProvider, and Pendle callData. | ||
| * @return dolaAmount The amount of DOLA redeemed. | ||
| */ | ||
| function transformFromCollateral( | ||
| uint256 amount, | ||
| address recipient, | ||
| bytes calldata data | ||
| ) public override returns (uint256 dolaAmount) { | ||
| ( | ||
| address market, | ||
| uint256 minOut, // Minimum amount of DOLA to receive | ||
| address ytProvider, | ||
| bytes memory callData | ||
| ) = abi.decode(data, (address, uint256, address, bytes)); | ||
| _revertIfMarketNotSet(market); | ||
|
|
||
| if (ytProvider != address(0)) { | ||
08xmt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| IERC20 yt = IERC20(markets[market].yt); | ||
| yt.safeTransferFrom(ytProvider, address(this), amount); | ||
| yt.approve(router, amount); | ||
| } | ||
|
|
||
| IERC20 pt = IERC20(markets[market].pt); | ||
| pt.safeTransferFrom(msg.sender, address(this), amount); | ||
| pt.approve(router, amount); | ||
|
|
||
| uint256 dolaBal = DOLA.balanceOf(recipient); | ||
| (bool success, ) = router.call(callData); | ||
08xmt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (!success) revert PendleSwapFailed(); | ||
| // Ensure recipient received at least minOut DOLA | ||
| dolaAmount = DOLA.balanceOf(recipient) - dolaBal; | ||
| if (dolaAmount < minOut) revert InsufficientDOLA(); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Convert DOLA to PT or PT and YT and deposit PT amount on behalf of recipient, sending YT to ytRecipient | ||
| * @param assets The receiver in Pendle API has to be set to this contract address. If a MINT is performed, provide a ytRecipient or YT will be kept in this contract. | ||
| * @param recipient The address on behalf of which the PT tokens are deposited. | ||
| * @param data The encoded address of the market. | ||
| * @return collateralAmount The amount of collateral deposited into the market. | ||
| */ | ||
| function transformToCollateralAndDeposit( | ||
| uint256 assets, | ||
| address recipient, | ||
| bytes calldata data | ||
| ) external override returns (uint256) { | ||
| (address market, , , ) = abi.decode( | ||
| data, | ||
| (address, uint256, address, bytes) | ||
| ); | ||
|
|
||
| // Convert DOLA to PT token | ||
| uint256 amount = transformToCollateral(assets, address(this), data); | ||
|
|
||
| // Deposit PT into Market | ||
| IERC20(markets[market].pt).approve(market, amount); | ||
| IMarket(market).deposit(recipient, amount); | ||
|
|
||
| return amount; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Withdraw the collateral from the market then convert to DOLA. | ||
| * @dev The receiver in Pendle API has to be set same as the recipient. If a REDEEM is performed, include a ytProvider with enough allowance. | ||
| * @param amount The amount of PT token to be withdrawn from the market. | ||
| * @param recipient The address to which DOLA is transferred. | ||
| * @param permit The permit data for the Market. | ||
| * @param data The encoded address of the market. | ||
| * @return dolaAmount The amount of DOLA redeemed. | ||
| */ | ||
| function withdrawAndTransformFromCollateral( | ||
| uint256 amount, | ||
| address recipient, | ||
| Permit calldata permit, | ||
| bytes calldata data | ||
| ) external override returns (uint256 dolaAmount) { | ||
| ( | ||
| address market, | ||
| uint256 minOut, | ||
| address ytProvider, | ||
| bytes memory callData | ||
| ) = abi.decode(data, (address, uint256, address, bytes)); | ||
| _revertIfMarketNotSet(market); | ||
|
|
||
| IMarket(market).withdrawOnBehalf( | ||
| msg.sender, | ||
| amount, | ||
| permit.deadline, | ||
| permit.v, | ||
| permit.r, | ||
| permit.s | ||
| ); | ||
|
|
||
| if (ytProvider != address(0)) { | ||
| IERC20 yt = IERC20(markets[market].yt); | ||
| yt.safeTransferFrom(ytProvider, address(this), amount); | ||
| yt.approve(router, amount); | ||
| } | ||
08xmt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| IERC20 pt = IERC20(markets[market].pt); | ||
| pt.approve(router, amount); | ||
|
|
||
| uint256 dolaBal = DOLA.balanceOf(recipient); | ||
| (bool success, ) = router.call(callData); | ||
| if (!success) revert PendleSwapFailed(); | ||
|
|
||
| dolaAmount = DOLA.balanceOf(recipient) - dolaBal; | ||
| if (dolaAmount < minOut) revert InsufficientDOLA(); | ||
| } | ||
|
|
||
| function _revertIfMarketNotSet(address market) internal view { | ||
| if (address(markets[market].pt) == address(0)) | ||
| revert MarketNotSet(market); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Set the market address and its associated Pendle PT and YT addresses. | ||
| * @dev Only callable by the governance. | ||
| * @param marketAddress The address of the market. | ||
| * @param ptAddress Pendle PT address | ||
| * @param ytAddress Pendle YT address | ||
| */ | ||
| function setMarket( | ||
| address marketAddress, | ||
| address ptAddress, | ||
| address ytAddress | ||
| ) external onlyGov { | ||
| markets[marketAddress] = PT({pt: ptAddress, yt: ytAddress}); | ||
| emit MarketSet(marketAddress, ptAddress, ytAddress); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Remove the market. | ||
| * @dev Only callable by the governance or the guardian. | ||
| * @param market The address of the market to be removed. | ||
| */ | ||
| function removeMarket(address market) external onlyGuardianOrGov { | ||
| delete markets[market]; | ||
| emit MarketRemoved(market); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.