Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.23;

import "../../SYBaseUpg.sol";
import "../../../../interfaces/Midas/IMidasDepositVault.sol";
import "../../../../interfaces/Midas/IMidasRedemptionVault.sol";
import "./libraries/DecimalsCorrectionLibrary.sol";
import "./libraries/MidasAdapterLib.sol";

contract PendleMidasSY is SYBaseUpg {
using DecimalsCorrectionLibrary for uint256;
using PMath for uint256;


bytes32 public constant PENDLE_REFERRER_ID = keccak256("midas.referrers.pendle");

// solhint-disable immutable-vars-naming
address public immutable depositVault;
address public immutable redemptionVault;
address public immutable mTokenDataFeed;
address public immutable underlying;

uint256 public immutable yieldTokenUnit;
uint256 public immutable underlyingUnit;

constructor(
address _mToken,
address _depositVault,
address _redemptionVault,
address _mTokenDataFeed,
address _underlying
) SYBaseUpg(_mToken) {
depositVault = _depositVault;
redemptionVault = _redemptionVault;
mTokenDataFeed = _mTokenDataFeed;
underlying = _underlying;

yieldTokenUnit = 10 ** MidasAdapterLib.getTokenDecimals(_mToken);
underlyingUnit = 10 ** MidasAdapterLib.getTokenDecimals(_underlying);
}

function initialize(string memory _name, string memory _symbol) external initializer {
__SYBaseUpg_init(_name, _symbol);
_safeApproveInf(yieldToken, redemptionVault);
}

function _deposit(
address tokenIn,
uint256 amountDeposited
) internal virtual override returns (uint256 /*amountSharesOut*/) {
if (tokenIn == yieldToken) {
return amountDeposited;
}

uint256 balanceBefore = _selfBalance(yieldToken);
_safeApproveInf(tokenIn, depositVault);
IMidasDepositVault(depositVault).depositInstant(
tokenIn,
MidasAdapterLib.tokenAmountToBase18(tokenIn, amountDeposited),
0,
PENDLE_REFERRER_ID
);
return _selfBalance(yieldToken) - balanceBefore;
}

function _redeem(
address receiver,
address tokenOut,
uint256 amountSharesToRedeem
) internal override returns (uint256 amountTokenOut) {

if (tokenOut == yieldToken) {
_transferOut(tokenOut, receiver, amountSharesToRedeem);
return amountSharesToRedeem;
}

uint256 balanceBefore = _selfBalance(tokenOut);
IMidasRedemptionVault(redemptionVault).redeemInstant(tokenOut, amountSharesToRedeem, 0);
amountTokenOut = _selfBalance(tokenOut) - balanceBefore;
_transferOut(tokenOut, receiver, amountTokenOut);
}

function exchangeRate() public view virtual override returns (uint256) {
return IMidasDataFeed(mTokenDataFeed).getDataInBase18() * underlyingUnit / yieldTokenUnit;
}

function _previewDeposit(
address tokenIn,
uint256 amountTokenToDeposit
) internal view override returns (uint256 /*amountSharesOut*/) {
if (tokenIn == yieldToken) {
return amountTokenToDeposit;
}

// amountTokenToDeposit is converted to base 18 inside lib
return MidasAdapterLib.estimateAmountOutDeposit(depositVault, mTokenDataFeed, tokenIn, amountTokenToDeposit);
}

function _previewRedeem(
address tokenOut,
uint256 amountSharesToRedeem
) internal view override returns (uint256 /*amountTokenOut*/) {
if (tokenOut == yieldToken) {
return amountSharesToRedeem;
}

// amountTokenOut is converted back to original decimals inside lib
return MidasAdapterLib.estimateAmountOutRedeem(redemptionVault, mTokenDataFeed, tokenOut, amountSharesToRedeem);
}

function getTokensIn() public view override returns (address[] memory res) {
return ArrayLib.append(IMidasManageableVault(depositVault).getPaymentTokens(), yieldToken);
}

function getTokensOut() public view override returns (address[] memory res) {
return ArrayLib.append(IMidasManageableVault(redemptionVault).getPaymentTokens(), yieldToken);
}

function isValidTokenIn(address token) public view override returns (bool) {
return token == yieldToken || IMidasManageableVault(depositVault).tokensConfig(token).dataFeed != address(0);
}

function isValidTokenOut(address token) public view override returns (bool) {
return token == yieldToken || IMidasManageableVault(redemptionVault).tokensConfig(token).dataFeed != address(0);
}

function assetInfo() external view returns (AssetType assetType, address assetAddress, uint8 assetDecimals) {
return (AssetType.TOKEN, underlying, MidasAdapterLib.getTokenDecimals(underlying));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.23;

/**
* @title DecimalsCorrectionLibrary
* @author RedDuck Software
*/
library DecimalsCorrectionLibrary {
/**
* @dev converts `originalAmount` with `originalDecimals` into
* amount with `decidedDecimals`
* @param originalAmount amount to convert
* @param originalDecimals decimals of the original amount
* @param decidedDecimals decimals for the output amount
* @return amount converted amount with `decidedDecimals`
*/
function convert(
uint256 originalAmount,
uint256 originalDecimals,
uint256 decidedDecimals
) internal pure returns (uint256) {
if (originalAmount == 0) return 0;
if (originalDecimals == decidedDecimals) return originalAmount;

uint256 adjustedAmount;

if (originalDecimals > decidedDecimals) {
adjustedAmount = originalAmount / (10 ** (originalDecimals - decidedDecimals));
} else {
adjustedAmount = originalAmount * (10 ** (decidedDecimals - originalDecimals));
}

return adjustedAmount;
}

/**
* @dev converts `originalAmount` with decimals 18 into
* amount with `decidedDecimals`
* @param originalAmount amount to convert
* @param decidedDecimals decimals for the output amount
* @return amount converted amount with `decidedDecimals`
*/
function convertFromBase18(uint256 originalAmount, uint256 decidedDecimals) internal pure returns (uint256) {
return convert(originalAmount, 18, decidedDecimals);
}

/**
* @dev converts `originalAmount` with `originalDecimals` into
* amount with decimals 18
* @param originalAmount amount to convert
* @param originalDecimals decimals of the original amount
* @return amount converted amount with 18 decimals
*/
function convertToBase18(uint256 originalAmount, uint256 originalDecimals) internal pure returns (uint256) {
return convert(originalAmount, originalDecimals, 18);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import "../../../../../interfaces/Midas/IMidasManageableVault.sol";
import "../../../../../interfaces/Midas/IMidasDataFeed.sol";
import "../../../../libraries/math/PMath.sol";
import "./DecimalsCorrectionLibrary.sol";

library MidasAdapterLib {
using DecimalsCorrectionLibrary for uint256;
using PMath for uint256;

uint256 private constant ONE_HUNDRED_PERCENT = 100 * 100;
uint256 private constant STABLECOIN_RATE = 10 ** 18;

function estimateAmountOutDeposit(
address depositVault,
address mTokenDataFeed,
address tokenIn,
uint256 amountTokenIn
) internal view returns (uint256) {
uint8 tokenDecimals = getTokenDecimals(tokenIn);
uint256 amountTokenInBase18 = tokenAmountToBase18(amountTokenIn, tokenDecimals);

IMidasManageableVault.TokenConfig memory tokenConfig = IMidasManageableVault(depositVault).tokensConfig(
tokenIn
);

uint256 tokenInRate = getTokenRate(tokenConfig.dataFeed, tokenConfig.stable);
require(tokenInRate > 0, "tokenInRate zero");

uint256 mTokenRate = getTokenRate(mTokenDataFeed, false);
require(mTokenRate > 0, "mTokenRate zero");

uint256 amountInUsd = (amountTokenInBase18 * tokenInRate) / PMath.ONE;

uint256 feeTokenAmount = _truncate(
_getFeeAmount(depositVault, tokenConfig, amountTokenInBase18),
tokenDecimals
);

uint256 feeInUsd = (feeTokenAmount * tokenInRate) / PMath.ONE;
uint256 amountInUsdWithoutFee = amountInUsd - feeInUsd;

uint256 amountMToken = (amountInUsdWithoutFee * (PMath.ONE)) / mTokenRate;

return amountMToken;
}

function estimateAmountOutRedeem(
address redemptionVault,
address mTokenDataFeed,
address tokenOut,
uint256 amountMTokenIn
) internal view returns (uint256 amountTokenOut) {
IMidasManageableVault.TokenConfig memory tokenConfig = IMidasManageableVault(redemptionVault).tokensConfig(
tokenOut
);

uint256 mTokenRate = getTokenRate(mTokenDataFeed, false);
require(mTokenRate > 0, "mTokenRate zero");

uint256 tokenOutRate = getTokenRate(tokenConfig.dataFeed, tokenConfig.stable);
require(tokenOutRate > 0, "tokenOutRate zero");

uint256 feeAmount = _getFeeAmount(redemptionVault, tokenConfig, amountMTokenIn);

uint256 amountMTokenWithoutFee = amountMTokenIn - feeAmount;

amountTokenOut = ((amountMTokenWithoutFee * mTokenRate) / tokenOutRate).convertFromBase18(
getTokenDecimals(tokenOut)
);
}

function getTokenRate(address dataFeed, bool stable) internal view returns (uint256) {
uint256 rate = IMidasDataFeed(dataFeed).getDataInBase18();
if (stable) return STABLECOIN_RATE;
return rate;
}

function getTokenDecimals(address token) internal view returns (uint8) {
return IERC20Metadata(token).decimals();
}

function tokenAmountToBase18(address token, uint256 amount) internal view returns (uint256) {
return amount.convertToBase18(getTokenDecimals(token));
}

function tokenAmountToBase18(uint256 amount, uint8 decimals) internal pure returns (uint256) {
return amount.convertToBase18(decimals);
}

function _truncate(uint256 value, uint8 decimals) private pure returns (uint256) {
return value.convertFromBase18(decimals).convertToBase18(decimals);
}

function _getFeeAmount(
address vault,
IMidasManageableVault.TokenConfig memory tokenConfig,
uint256 amount
) private view returns (uint256) {
if (IMidasManageableVault(vault).waivedFeeRestriction(address(this))) return 0;

uint256 feePercent;

feePercent = tokenConfig.fee;

feePercent += IMidasManageableVault(vault).instantFee();

if (feePercent > ONE_HUNDRED_PERCENT) feePercent = ONE_HUNDRED_PERCENT;

return (amount * feePercent) / ONE_HUNDRED_PERCENT;
}
}
6 changes: 6 additions & 0 deletions contracts/interfaces/Midas/IMidasDataFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMidasDataFeed {
function getDataInBase18() external view returns (uint256 answer);
}
13 changes: 13 additions & 0 deletions contracts/interfaces/Midas/IMidasDepositVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IMidasManageableVault.sol";

interface IMidasDepositVault is IMidasManageableVault {
function depositInstant(
address tokenIn,
uint256 amountToken,
uint256 minReceiveAmount,
bytes32 referrerId
) external;
}
19 changes: 19 additions & 0 deletions contracts/interfaces/Midas/IMidasManageableVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMidasManageableVault {
struct TokenConfig {
address dataFeed;
uint256 fee;
uint256 allowance;
bool stable;
}

function getPaymentTokens() external view returns (address[] memory);

function tokensConfig(address token) external view returns (TokenConfig memory);

function instantFee() external view returns (uint256);

function waivedFeeRestriction(address sender) external view returns (bool);
}
8 changes: 8 additions & 0 deletions contracts/interfaces/Midas/IMidasRedemptionVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IMidasManageableVault.sol";

interface IMidasRedemptionVault is IMidasManageableVault {
function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount) external;
}
10 changes: 5 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}