Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
11 changes: 11 additions & 0 deletions src/accounting/oracles/AbstractYieldSourceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ abstract contract AbstractYieldSourceOracle is IYieldSourceOracle {
virtual
returns (uint256);

/// @inheritdoc IYieldSourceOracle
function quoteWithdrawalAssets(
address yieldSourceAddress,
address assetIn,
uint256 assetsIn
)
external
view
virtual
returns (uint256);

/// @inheritdoc IYieldSourceOracle
function getAssetOutput(
address yieldSourceAddress,
Expand Down
14 changes: 14 additions & 0 deletions src/accounting/oracles/ERC4626YieldSourceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ contract ERC4626YieldSourceOracle is AbstractYieldSourceOracle {
return IERC4626(yieldSourceAddress).previewDeposit(assetsIn);
}

/// @inheritdoc AbstractYieldSourceOracle
function quoteWithdrawalAssets(
address yieldSourceAddress,
address,
uint256 assetsIn
)
external
view
override
returns (uint256)
{
return IERC4626(yieldSourceAddress).previewWithdraw(assetsIn);
}

/// @inheritdoc AbstractYieldSourceOracle
function getAssetOutput(
address yieldSourceAddress,
Expand Down
28 changes: 26 additions & 2 deletions src/accounting/oracles/ERC5115YieldSourceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,25 @@ contract ERC5115YieldSourceOracle is AbstractYieldSourceOracle {
override
returns (uint256)
{
return IStandardizedYield(yieldSourceAddress).previewDeposit(assetIn, assetsIn);
return _getShareOutput(yieldSourceAddress, assetIn, assetsIn);
}

/// @inheritdoc AbstractYieldSourceOracle
function quoteWithdrawalAssets(
address yieldSourceAddress,
address assetIn,
uint256 assetsIn
)
external
view
override
returns (uint256)
{
uint256 obtainableShares = _getShareOutput(yieldSourceAddress, assetIn, assetsIn);
uint256 obtainableAssets = _getAssetOutput(yieldSourceAddress, assetIn, obtainableShares);
return obtainableAssets;
}

/// @inheritdoc AbstractYieldSourceOracle
function getAssetOutput(
address yieldSourceAddress,
Expand All @@ -47,7 +63,7 @@ contract ERC5115YieldSourceOracle is AbstractYieldSourceOracle {
override
returns (uint256)
{
return IStandardizedYield(yieldSourceAddress).previewRedeem(assetOut, sharesIn);
return _getAssetOutput(yieldSourceAddress, assetOut, sharesIn);
}

/// @inheritdoc AbstractYieldSourceOracle
Expand Down Expand Up @@ -91,4 +107,12 @@ contract ERC5115YieldSourceOracle is AbstractYieldSourceOracle {
if (totalShares == 0) return 0;
return Math.mulDiv(totalShares, yieldSource.exchangeRate(), 1e18);
}

function _getAssetOutput(address yieldSourceAddress, address assetIn, uint256 assetsIn) internal view returns (uint256) {
return IStandardizedYield(yieldSourceAddress).previewRedeem(assetIn, assetsIn);
}

function _getShareOutput(address yieldSourceAddress, address assetIn, uint256 assetsIn) internal view returns (uint256) {
return IStandardizedYield(yieldSourceAddress).previewDeposit(assetIn, assetsIn);
}
}
28 changes: 26 additions & 2 deletions src/accounting/oracles/ERC7540YieldSourceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,23 @@ contract ERC7540YieldSourceOracle is AbstractYieldSourceOracle {
override
returns (uint256)
{
return IERC7540(yieldSourceAddress).convertToShares(assetsIn);
return _getShareOutput(yieldSourceAddress, assetsIn);
}

/// @inheritdoc AbstractYieldSourceOracle
function quoteWithdrawalAssets(
address yieldSourceAddress,
address,
uint256 assetsIn
)
external
view
override
returns (uint256)
{
uint256 obtainableShares = _getShareOutput(yieldSourceAddress, assetsIn);
uint256 obtainableAssets = _getAssetOutput(yieldSourceAddress, obtainableShares);
return obtainableAssets;
}

/// @inheritdoc AbstractYieldSourceOracle
Expand All @@ -49,7 +65,7 @@ contract ERC7540YieldSourceOracle is AbstractYieldSourceOracle {
override
returns (uint256)
{
return IERC7540(yieldSourceAddress).convertToAssets(sharesIn);
return _getAssetOutput(yieldSourceAddress, sharesIn);
}

/// @inheritdoc AbstractYieldSourceOracle
Expand Down Expand Up @@ -91,4 +107,12 @@ contract ERC7540YieldSourceOracle is AbstractYieldSourceOracle {
function getTVL(address yieldSourceAddress) public view override returns (uint256) {
return IERC7540(yieldSourceAddress).totalAssets();
}

function _getShareOutput(address yieldSourceAddress, uint256 assetsIn) internal view returns (uint256) {
return IERC7540(yieldSourceAddress).convertToShares(assetsIn);
}

function _getAssetOutput(address yieldSourceAddress, uint256 sharesIn) internal view returns (uint256) {
return IERC7540(yieldSourceAddress).convertToAssets(sharesIn);
}
}
110 changes: 67 additions & 43 deletions src/accounting/oracles/PendlePTYieldSourceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,30 +59,7 @@ contract PendlePTYieldSourceOracle is AbstractYieldSourceOracle {
override
returns (uint256 sharesOut)
{
uint256 pricePerShare = getPricePerShare(market); // Price is PT/Asset in 1e18
if (pricePerShare == 0) return 0; // Avoid division by zero

// sharesOut = assetsIn * 1e18 / pricePerShare
// Asset decimals might differ from 18, need to adjust. PT decimals also matter.
IStandardizedYield sY = IStandardizedYield(_sy(market));
(,, uint8 assetDecimals) = _getAssetInfo(sY);

uint8 ptDecimals = IERC20Metadata(_pt(market)).decimals();

// Scale assetsIn to Price Decimals (1e18) before calculating shares
uint256 assetsIn18;
if (assetDecimals <= PRICE_DECIMALS) {
// Scale up if assetDecimals <= 18
assetsIn18 = assetsIn * (10 ** (PRICE_DECIMALS - assetDecimals));
} else {
// Scale down if assetDecimals > 18
// Avoids underflow in 10**(PRICE_DECIMALS - assetDecimals)
assetsIn18 = Math.mulDiv(assetsIn, 1, 10 ** (assetDecimals - PRICE_DECIMALS));
}

// Result is in PT decimals: sharesOut = assetsIn18 * 1e(ptDecimals) / pricePerShare
// pricePerShare is PT/Asset in 1e18
sharesOut = Math.mulDiv(assetsIn18, 10 ** uint256(ptDecimals), pricePerShare);
return _getShareOutput(market, assetsIn);
}

/// @inheritdoc IYieldSourceOracle
Expand All @@ -96,26 +73,23 @@ contract PendlePTYieldSourceOracle is AbstractYieldSourceOracle {
override
returns (uint256 assetsOut)
{
uint256 pricePerShare = getPricePerShare(market); // Price is PT/Asset in 1e18

// assetsOut = sharesIn * pricePerShare / 1e(ptDecimals) / 1e(18 - assetDecimals)
uint8 ptDecimals = IERC20Metadata(_pt(market)).decimals();
IStandardizedYield sY = IStandardizedYield(_sy(market));
(,, uint8 assetDecimals) = _getAssetInfo(sY);

// Calculate asset value in 1e18 terms first
// assetsOut18 = sharesIn * pricePerShare / 10^ptDecimals
uint256 assetsOut18 = Math.mulDiv(sharesIn, pricePerShare, 10 ** uint256(ptDecimals));
return _getAssetOutput(market, sharesIn);
}

// Scale from 1e18 representation (PRICE_DECIMALS) to asset's actual decimals
if (assetDecimals >= PRICE_DECIMALS) {
// Scale up if assetDecimals >= 18
assetsOut = assetsOut18 * (10 ** (assetDecimals - PRICE_DECIMALS));
} else {
// Scale down if assetDecimals < 18
// Avoids underflow in 10**(PRICE_DECIMALS - assetDecimals) which happens in the division below
assetsOut = Math.mulDiv(assetsOut18, 1, 10 ** (PRICE_DECIMALS - assetDecimals));
}
/// @inheritdoc AbstractYieldSourceOracle
function quoteWithdrawalAssets(
address market,
address,
uint256 assetsIn
)
external
view
override
returns (uint256)
{
uint256 obtainableShares = _getShareOutput(market, assetsIn);
uint256 obtainableAssets = _getAssetOutput(market, obtainableShares);
return obtainableAssets;
}

/// @inheritdoc IYieldSourceOracle
Expand Down Expand Up @@ -160,6 +134,56 @@ contract PendlePTYieldSourceOracle is AbstractYieldSourceOracle {
balance = pt.balanceOf(ownerOfShares);
}

function _getShareOutput(address market, uint256 assetsIn) internal view returns (uint256 sharesOut) {
uint256 pricePerShare = getPricePerShare(market); // Price is PT/Asset in 1e18
if (pricePerShare == 0) return 0; // Avoid division by zero

// sharesOut = assetsIn * 1e18 / pricePerShare
// Asset decimals might differ from 18, need to adjust. PT decimals also matter.
IStandardizedYield sY = IStandardizedYield(_sy(market));
(,, uint8 assetDecimals) = _getAssetInfo(sY);

uint8 ptDecimals = IERC20Metadata(_pt(market)).decimals();

// Scale assetsIn to Price Decimals (1e18) before calculating shares
uint256 assetsIn18;
if (assetDecimals <= PRICE_DECIMALS) {
// Scale up if assetDecimals <= 18
assetsIn18 = assetsIn * (10 ** (PRICE_DECIMALS - assetDecimals));
} else {
// Scale down if assetDecimals > 18
// Avoids underflow in 10**(PRICE_DECIMALS - assetDecimals)
assetsIn18 = Math.mulDiv(assetsIn, 1, 10 ** (assetDecimals - PRICE_DECIMALS));
}

// Result is in PT decimals: sharesOut = assetsIn18 * 1e(ptDecimals) / pricePerShare
// pricePerShare is PT/Asset in 1e18
sharesOut = Math.mulDiv(assetsIn18, 10 ** uint256(ptDecimals), pricePerShare);
}

function _getAssetOutput(address market, uint256 sharesIn) internal view returns (uint256 assetsOut) {
uint256 pricePerShare = getPricePerShare(market); // Price is PT/Asset in 1e18

// assetsOut = sharesIn * pricePerShare / 1e(ptDecimals) / 1e(18 - assetDecimals)
uint8 ptDecimals = IERC20Metadata(_pt(market)).decimals();
IStandardizedYield sY = IStandardizedYield(_sy(market));
(,, uint8 assetDecimals) = _getAssetInfo(sY);

// Calculate asset value in 1e18 terms first
// assetsOut18 = sharesIn * pricePerShare / 10^ptDecimals
uint256 assetsOut18 = Math.mulDiv(sharesIn, pricePerShare, 10 ** uint256(ptDecimals));

// Scale from 1e18 representation (PRICE_DECIMALS) to asset's actual decimals
if (assetDecimals >= PRICE_DECIMALS) {
// Scale up if assetDecimals >= 18
assetsOut = assetsOut18 * (10 ** (assetDecimals - PRICE_DECIMALS));
} else {
// Scale down if assetDecimals < 18
// Avoids underflow in 10**(PRICE_DECIMALS - assetDecimals) which happens in the division below
assetsOut = Math.mulDiv(assetsOut18, 1, 10 ** (PRICE_DECIMALS - assetDecimals));
}
}

function _getAssetInfo(IStandardizedYield sY) internal view returns (uint256, address, uint8) {
(IStandardizedYield.AssetType assetType, address assetAddress, uint8 assetDecimals) = sY.assetInfo();

Expand Down
28 changes: 26 additions & 2 deletions src/accounting/oracles/SpectraPTYieldSourceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,25 @@ contract SpectraPTYieldSourceOracle is AbstractYieldSourceOracle {
/// @inheritdoc AbstractYieldSourceOracle
function getShareOutput(address ptAddress, address, uint256 assetsIn) external view override returns (uint256) {
// Use convertToPrincipal to get shares (PTs) for assets
return IPrincipalToken(ptAddress).convertToPrincipal(assetsIn);
return _getShareOutput(ptAddress, assetsIn);
}

/// @inheritdoc AbstractYieldSourceOracle
function quoteWithdrawalAssets(
address ptAddress,
address,
uint256 assetsIn
)
external
view
override
returns (uint256)
{
uint256 obtainableShares = _getShareOutput(ptAddress, assetsIn);
uint256 obtainableAssets = _getAssetOutput(ptAddress, obtainableShares);
return obtainableAssets;
}

/// @inheritdoc AbstractYieldSourceOracle
function getAssetOutput(
address ptAddress,
Expand All @@ -41,7 +57,7 @@ contract SpectraPTYieldSourceOracle is AbstractYieldSourceOracle {
returns (uint256)
{
// Use convertToUnderlying to get assets for shares (PTs)
return IPrincipalToken(ptAddress).convertToUnderlying(sharesIn);
return _getAssetOutput(ptAddress, sharesIn);
}

/// @inheritdoc AbstractYieldSourceOracle
Expand Down Expand Up @@ -80,4 +96,12 @@ contract SpectraPTYieldSourceOracle is AbstractYieldSourceOracle {
function _balanceOf(address ptAddress, address owner) internal view returns (uint256) {
return IERC20Metadata(ptAddress).balanceOf(owner);
}

function _getShareOutput(address ptAddress, uint256 assetsIn) internal view returns (uint256 sharesOut) {
return IPrincipalToken(ptAddress).convertToPrincipal(assetsIn);
}

function _getAssetOutput(address ptAddress, uint256 sharesIn) internal view returns (uint256 assetsOut) {
return IPrincipalToken(ptAddress).convertToUnderlying(sharesIn);
}
}
14 changes: 14 additions & 0 deletions src/accounting/oracles/StakingYieldSourceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ contract StakingYieldSourceOracle is AbstractYieldSourceOracle {
return assetsIn;
}

/// @inheritdoc AbstractYieldSourceOracle
function quoteWithdrawalAssets(
address,
address,
uint256 assetsIn
)
external
view
override
returns (uint256)
{
return assetsIn;
}

/// @inheritdoc AbstractYieldSourceOracle
function getAssetOutput(address, address, uint256 sharesIn) public pure override returns (uint256) {
return sharesIn;
Expand Down
15 changes: 15 additions & 0 deletions src/interfaces/accounting/IYieldSourceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@ interface IYieldSourceOracle {
view
returns (uint256);

/// @notice Calculates the number of shares that would be received for a given amount of assets
/// @dev Used for withdrawal simulations and to calculate current exchange rates
/// @param yieldSourceAddress The yield-bearing token address (e.g., aUSDC, cDAI)
/// @param assetIn The underlying asset being withdrawn (e.g., USDC, DAI)
/// @param assetsIn The amount of underlying assets to withdraw, in the asset's native units
/// @return assets The number of underlying assets that would be received
function quoteWithdrawalAssets(
address yieldSourceAddress,
address assetIn,
uint256 assetsIn
)
external
view
returns (uint256);

/// @notice Calculates the number of underlying assets that would be received for a given amount of shares
/// @dev Used for withdrawal simulations and to calculate current yield
/// @param yieldSourceAddress The yield-bearing token address (e.g., aUSDC, cDAI)
Expand Down
4 changes: 4 additions & 0 deletions test/mocks/MockYieldSourceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ contract MockYieldSourceOracle is IYieldSourceOracle {
return assetsIn;
}

function quoteWithdrawalAssets(address, address, uint256 assetsIn) external pure override returns (uint256) {
return assetsIn;
}

function getAssetOutput(address, address, uint256 sharesIn) public pure returns (uint256) {
return sharesIn;
}
Expand Down
4 changes: 4 additions & 0 deletions test/unit/accounting/AbstractYieldSourceOracleTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ contract MockYieldSourceOracle is AbstractYieldSourceOracle {
return assetsIn;
}

function quoteWithdrawalAssets(address, address, uint256 assetsIn) external pure override returns (uint256) {
return assetsIn;
}

function getAssetOutput(address, address, uint256 sharesIn) public pure override returns (uint256) {
return sharesIn;
}
Expand Down
Loading