-
Notifications
You must be signed in to change notification settings - Fork 18
ERC4626 adapter for siUSD #9
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
base: main
Are you sure you want to change the base?
Changes from all commits
932b155
9853191
75d7c7b
cdd9492
1cfc3b9
5fc7656
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
| pragma solidity ^0.8.17; | ||
|
|
||
| import "../../SYBaseUpg.sol"; | ||
| import "../../../../interfaces/IERC4626.sol"; | ||
|
|
||
| import "../PendleERC4626UpgSYV2.sol"; | ||
|
|
||
| interface IInfiniFiGateway { | ||
| function stake(address _to, uint256 _receiptTokens) external returns (uint256); | ||
|
|
||
| function redeem(address _to, uint256 _amount, uint256 _minAssetsOut) external returns (uint256); | ||
|
|
||
| function unstake(address _to, uint256 _stakedTokens) external returns (uint256); | ||
|
|
||
| function getAddress(string memory _name) external view returns (address); | ||
|
|
||
| function mintAndStake(address _to, uint256 _amount) external returns (uint256); | ||
| } | ||
|
|
||
| interface IYieldSharing { | ||
| function vested() external view returns (uint256); | ||
| } | ||
|
|
||
| interface IRedeemController { | ||
| function receiptToAsset(uint256 _receiptAmount) external view returns (uint256); | ||
| } | ||
|
|
||
| interface IMintController { | ||
| function assetToReceipt(uint256 _assetAmount) external view returns (uint256); | ||
| } | ||
|
|
||
| contract PendleInfinifiSIUSD is PendleERC4626UpgSYV2 { | ||
| using PMath for uint256; | ||
|
|
||
| address public constant IUSD = 0x48f9e38f3070AD8945DFEae3FA70987722E3D89c; | ||
| address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; | ||
| address public constant SIUSD = 0xDBDC1Ef57537E34680B898E1FEBD3D68c7389bCB; | ||
| address public constant GATEWAY = 0x3f04b65Ddbd87f9CE0A2e7Eb24d80e7fb87625b5; | ||
|
|
||
| error UnsupportedToken(address _token); | ||
|
|
||
| constructor() PendleERC4626UpgSYV2(SIUSD) {} | ||
|
|
||
| function initialize(string memory _name, string memory _symbol) external override initializer { | ||
| __SYBaseUpg_init(_name, _symbol); | ||
| _safeApproveInf(IUSD, GATEWAY); | ||
| _safeApproveInf(USDC, GATEWAY); | ||
| _safeApproveInf(SIUSD, GATEWAY); | ||
| } | ||
|
|
||
| function _deposit( | ||
| address tokenIn, | ||
| uint256 amountDeposited | ||
| ) internal override returns (uint256 /*amountSharesOut*/) { | ||
| if (tokenIn == IUSD) { | ||
| return IInfiniFiGateway(GATEWAY).stake(address(this), amountDeposited); | ||
| } | ||
|
|
||
| if (tokenIn == USDC) { | ||
| uint256 amountBefore = IERC4626(SIUSD).balanceOf(address(this)); | ||
| IInfiniFiGateway(GATEWAY).mintAndStake(address(this), amountDeposited); | ||
| uint256 amountAfter = IERC4626(SIUSD).balanceOf(address(this)); | ||
| return amountAfter - amountBefore; | ||
| } | ||
|
|
||
| if (tokenIn == SIUSD) { | ||
| return amountDeposited; | ||
| } | ||
|
|
||
| revert UnsupportedToken(tokenIn); | ||
| } | ||
|
|
||
| function _redeem( | ||
| address receiver, | ||
| address tokenOut, | ||
| uint256 amountSharesToRedeem | ||
| ) internal override returns (uint256 /*amountTokenOut*/) { | ||
| if (tokenOut == IUSD) { | ||
| return IInfiniFiGateway(GATEWAY).unstake(receiver, amountSharesToRedeem); | ||
| } | ||
|
|
||
| if (tokenOut == USDC) { | ||
| // unstake from siUSD to iUSD | ||
| uint256 receiptOut = IInfiniFiGateway(GATEWAY).unstake(address(this), amountSharesToRedeem); | ||
| address redeemController = IInfiniFiGateway(GATEWAY).getAddress("redeemController"); | ||
| // convert iUSD to USDC | ||
| uint256 assetsOut = IRedeemController(redeemController).receiptToAsset(receiptOut); | ||
| // redeem iUSD, assetsOut is in USDC | ||
| return IInfiniFiGateway(GATEWAY).redeem(receiver, receiptOut, assetsOut); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the InfiniFi protocol's redemption mechanism, when immediate liquidity is insufficient during In the Pendle integration, function claimRedemption() external whenNotPaused nonReentrant {
RedeemController(getAddress("redeemController")).claimRedemption(msg.sender);
}From uint256 amount = userPendingClaims[_recipient];
require(amount > 0, NoPendingClaims(_recipient));
userPendingClaims[_recipient] = 0;
totalPendingClaims -= amount;
emit RedemptionClaimed(block.timestamp, _recipient, amount);
return amount;(Note: The actual asset transfer occurs in This design ensures claims are secure and recipient-specific but introduces challenges:
The overall impact is that while funds are not permanently lost (they remain claimable), they could become inaccessible without manual intervention.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Setting assets out (usdc) to match the requested iUSD amount is going to prevent these requests from going into the queue There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I see what you mean: function redeem(address _to, uint256 _amount, uint256 _minAssetsOut)
external
whenNotPaused
nonReentrant
returns (uint256)
{
_revertIfThereAreUnaccruedLosses();
ReceiptToken iusd = ReceiptToken(getAddress("receiptToken"));
RedeemController redeemController = RedeemController(getAddress("redeemController"));
iusd.transferFrom(msg.sender, address(this), _amount);
iusd.approve(address(redeemController), _amount);
uint256 assetsOut = redeemController.redeem(_to, _amount);
require(assetsOut >= _minAssetsOut, MinAssetsOutError(_minAssetsOut, assetsOut)); // <--------------
return assetsOut;
}Totally agree. Updated the issue here: #14 |
||
| } | ||
|
|
||
| if (tokenOut == SIUSD) { | ||
| _transferOut(SIUSD, receiver, amountSharesToRedeem); | ||
| return amountSharesToRedeem; | ||
| } | ||
|
|
||
| revert UnsupportedToken(tokenOut); | ||
| } | ||
|
|
||
| // returns exchange rate in USDC | ||
| function exchangeRate() public view override returns (uint256) { | ||
| address redeemController = IInfiniFiGateway(GATEWAY).getAddress("redeemController"); | ||
| uint256 receiptOut = _convertToAssets(PMath.ONE); | ||
| return IRedeemController(redeemController).receiptToAsset(receiptOut); | ||
| } | ||
|
|
||
| function _previewDeposit( | ||
| address tokenIn, | ||
| uint256 amountTokenToDeposit | ||
| ) internal view override returns (uint256 /*amountSharesOut*/) { | ||
| if (tokenIn == IUSD) { | ||
| return _convertToShares(amountTokenToDeposit); | ||
| } | ||
|
|
||
| if (tokenIn == USDC) { | ||
| address mintController = IInfiniFiGateway(GATEWAY).getAddress("mintController"); | ||
| // preview USDC to iUSD conversion | ||
| uint256 receiptTokens = IMintController(mintController).assetToReceipt(amountTokenToDeposit); | ||
| return _convertToShares(receiptTokens); | ||
| } | ||
|
|
||
| if (tokenIn == SIUSD) { | ||
| return amountTokenToDeposit; | ||
| } | ||
|
|
||
| revert UnsupportedToken(tokenIn); | ||
| } | ||
|
|
||
| function _previewRedeem( | ||
| address tokenOut, | ||
| uint256 amountSharesToRedeem | ||
| ) internal view override returns (uint256 /*amountTokenOut*/) { | ||
| if (tokenOut == IUSD) { | ||
| return _convertToAssets(amountSharesToRedeem); | ||
| } | ||
|
|
||
| if (tokenOut == USDC) { | ||
| // see how much iUSD we get from redeeming siUSD | ||
| uint256 receiptOut = _convertToAssets(amountSharesToRedeem); | ||
| address redeemController = IInfiniFiGateway(GATEWAY).getAddress("redeemController"); | ||
| // convert iUSD to USDC | ||
| return IRedeemController(redeemController).receiptToAsset(receiptOut); | ||
| } | ||
|
|
||
| if (tokenOut == SIUSD) { | ||
| return amountSharesToRedeem; | ||
| } | ||
|
|
||
| revert UnsupportedToken(tokenOut); | ||
| } | ||
|
|
||
| function _convertToShares(uint256 _receiptIn) internal view returns (uint256 /* _sharesOut */) { | ||
| uint256 vested = IYieldSharing(IInfiniFiGateway(GATEWAY).getAddress("yieldSharing")).vested(); | ||
| uint256 supply = IERC4626(SIUSD).totalSupply(); | ||
| uint256 assets = IERC4626(SIUSD).totalAssets() + vested; | ||
| return supply == 0 ? _receiptIn : ((_receiptIn * supply) / assets); | ||
| } | ||
|
|
||
| function _convertToAssets(uint256 _sharesIn) internal view returns (uint256 /* _assetsOut */) { | ||
| uint256 vested = IYieldSharing(IInfiniFiGateway(GATEWAY).getAddress("yieldSharing")).vested(); | ||
| uint256 supply = IERC4626(SIUSD).totalSupply(); | ||
| uint256 assets = IERC4626(SIUSD).totalAssets() + vested; | ||
| return supply == 0 ? _sharesIn : (_sharesIn * assets) / supply; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lol. My LSP was jumping to Solmate implementation. I see now that this is open zeppelin version. Weird. |
||
| } | ||
|
|
||
| function getTokensIn() public pure override returns (address[] memory res) { | ||
| res = new address[](3); | ||
| res[0] = IUSD; | ||
| res[1] = SIUSD; | ||
| res[2] = USDC; | ||
| } | ||
|
|
||
| function getTokensOut() public pure override returns (address[] memory res) { | ||
| res = new address[](3); | ||
| res[0] = IUSD; | ||
| res[1] = SIUSD; | ||
| res[2] = USDC; | ||
| } | ||
|
|
||
| function isValidTokenIn(address token) public pure override returns (bool) { | ||
| return token == IUSD || token == SIUSD || token == USDC; | ||
| } | ||
|
|
||
| function isValidTokenOut(address token) public pure override returns (bool) { | ||
| return token == IUSD || token == SIUSD || token == USDC; | ||
| } | ||
|
|
||
| function assetInfo() | ||
| external | ||
| pure | ||
| override | ||
| returns (AssetType assetType, address assetAddress, uint8 assetDecimals) | ||
| { | ||
| return (AssetType.TOKEN, USDC, 6); | ||
| } | ||
| } | ||

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.
Sharing here my actual testing script. It can be used to see some of the issues already mentioned: