Skip to content
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

Present value oracle #342

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
33 changes: 33 additions & 0 deletions contracts/interfaces/IETHStakingProviderStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.10;

/**
* @title IETHStakingProviderStrategy
*
* @notice Interface for the calculation of current eth derivative parameters
*/
interface IETHStakingProviderStrategy {
struct TokenInfo {
StakingProvider provider;
uint64 exitEpoch;
uint64 withdrawableEpoch;
uint256 balance;
uint256 withdrawableTime;
}

function getTokenPresentValue(TokenInfo tokenInfo, uint256 discountRate)
external
returns (uint256 price);

function getDiscountRate(TokenInfo tokenInfo, uint256 borrowRate)
external
returns (uint256 discountRate);

function getSlashingRisk(uint256 tokenId)
external
returns (uint256 slashingRisk);

function getStakingRate(uint256 tokenId)
external
returns (uint256 stakingRate);
}
157 changes: 157 additions & 0 deletions contracts/misc/ETHUnstakePresentValue.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.10;

import {WadRayMath} from "../protocol/libraries/math/WadRayMath.sol";
import {IACLManager} from "../interfaces/IACLManager.sol";
import {IPoolAddressesProvider} from "../interfaces/IPoolAddressesProvider.sol";

contract ETHUnstakePresentValue {
enum StakingProvider {
Validator,
Lido,
RocketPool,
Coinbase
}

struct ParaETH {
uint256 principal;
uint256 unstakeTime;
StakingProvider provider;
uint256 underlyingTokenId; // in case there's an NFT representing the withdraw
uint256 unclaimedRewards;
}

struct ProviderConfiguration {
uint64 slashingRisk;
uint64 discountRate;
uint64 stakingRate;
}

IPoolAddressesProvider internal immutable ADDRESSES_PROVIDER;

mapping(uint256 => ProviderConfiguration) providerConfiguration;

/**
* @dev Constructor.
* @param provider The address of the PoolAddressesProvider contract
*/
constructor(IPoolAddressesProvider provider) {
ADDRESSES_PROVIDER = provider;
}

/**
* @dev Only pool admin can call functions marked by this modifier.
**/
modifier onlyPoolAdmin() {
_onlyPoolAdmin();
_;
}

function _onlyPoolAdmin() internal view virtual {
require(
IACLManager(ADDRESSES_PROVIDER.getACLManager()).isPoolAdmin(
msg.sender
),
Errors.CALLER_NOT_POOL_ADMIN
);
}

function getTokenPrice(uint256 tokenId) external returns (uint256 price) {
ParaETH tokenInfo = getTokenInfo(tokenId);

if (tokenInfo.provider == StakingProvider.Lido) {
return calculateLidoValue(tokenInfo);
} else if (tokenInfo.provider == StakingProvider.Validator) {
return calculateValidatorValue(tokenInfo);
} else {
return calculateOtherLSDValue(tokenInfo);
}
}

function calculateLidoValue(ParaETH tokenInfo)
internal
returns (uint256 price)
{
uint256 redemptionRate = getLidoRedemptionRate(
tokenInfo.underlyingTokenId
);
(uint64 slashingRisk, uint64 discountRate, ) = getProviderConfiguration(
tokenInfo.provider
);
uint256 unstakeTime = slashingRisk *
tokenInfo.unstakeTime -
block.timestamp;
// TODO convert unstakeTime to days
return
(tokenInfo.principal *
redemptionRate *
(WadRayMath.WAD - unstakeTime)) /
(WadRayMath.WAD + discountRate)**unstakeTime;
}

function calculateOtherLSDValue(ParaETH tokenInfo)
internal
returns (uint256 price)
{
(
uint64 slashingRisk,
uint64 discountRate,
uint64 stakingRate
) = getProviderConfiguration(tokenInfo.provider);
uint256 unstakeTime = slashingRisk *
tokenInfo.unstakeTime -
block.timestamp;

return
(tokenInfo.principal *
(WadRayMath.WAD - slashingRisk * unstakeTime)) /
(WadRayMath.WAD + discountRate)**unstakeTime;
}

function calculateValidatorValue(ParaETH tokenInfo)
internal
returns (uint256 price)
{
(
uint64 slashingRisk,
uint64 discountRate,
uint64 stakingRate
) = getProviderConfiguration(tokenInfo.provider);
uint256 unstakeTime = slashingRisk *
tokenInfo.unstakeTime -
block.timestamp;

return
(tokenInfo.principal *
(WadRayMath.WAD - slashingRisk * unstakeTime)) /
(WadRayMath.WAD + discountRate)**unstakeTime;
}

function getProviderConfiguration(StakingProvider provider)
public
returns (
uint64 slashingRisk,
uint64 discountRate,
uint64 stakingRate
)
{
ProviderConfiguration memory configs = providerConfiguration[provider];

return (
configs.slashingRisk,
configs.discountRate,
configs.stakingRate
);
}

function setProviderConfiguration(
StakingProvider provider,
ProviderConfiguration memory configs
) external onlyPoolAdmin {
providerConfiguration[provider] = configs;
}

function getLidoRedemptionRate(uint256 tokenId) internal returns (uint256) {
return WadRayMath.WAD;
}
}
87 changes: 87 additions & 0 deletions contracts/misc/ETHUnstakePresentValueV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.10;

import {WadRayMath} from "../protocol/libraries/math/WadRayMath.sol";
import {IACLManager} from "../interfaces/IACLManager.sol";
import {IPoolAddressesProvider} from "../interfaces/IPoolAddressesProvider.sol";
import {IETHStakingProviderStrategy} from "../interfaces/IETHStakingProviderStrategy.sol";

contract ETHUnstakePresentValue {
enum StakingProvider {
Validator,
Lido,
RocketPool,
Coinbase
}

struct TokenInfo {
StakingProvider provider;
uint64 exitEpoch;
uint64 withdrawableEpoch;
uint256 balance;
uint256 withdrawableTime;
}

struct ProviderConfiguration {
uint64 slashingRisk;
uint64 discountRate;
uint64 stakingRate;
}

IPoolAddressesProvider internal immutable ADDRESSES_PROVIDER;

mapping(uint256 => address) providerStrategyAddress;

/**
* @dev Constructor.
* @param provider The address of the PoolAddressesProvider contract
*/
constructor(IPoolAddressesProvider provider) {
ADDRESSES_PROVIDER = provider;
}

/**
* @dev Only pool admin can call functions marked by this modifier.
**/
modifier onlyPoolAdmin() {
_onlyPoolAdmin();
_;
}

function _onlyPoolAdmin() internal view virtual {
require(
IACLManager(ADDRESSES_PROVIDER.getACLManager()).isPoolAdmin(
msg.sender
),
Errors.CALLER_NOT_POOL_ADMIN
);
}

function getPresentValueAndDiscountRate(uint256 tokenId, uint256 borrowRate)
external
returns (uint256 price, uint256 discountRate)
{
TokenInfo tokenInfo = getTokenInfo(tokenId);

IETHStakingProviderStrategy strategy = providerStrategyAddress[
tokenInfo.provider
];

discountRate = strategy.getDiscountRate(tokenInfo, borrowRate);
price = strategy.getTokenPresentValue(tokenInfo, discountRate);
}

function getPresentValueByDiscountRate(
uint256 tokenId,
uint256 discountRate
) external returns (uint256 price) {
price = strategy.getTokenPresentValue(tokenInfo, discountRate);
}

function setProviderStrategyAddress(
StakingProvider provider,
address strategy
) external onlyPoolAdmin {
providerConfiguration[provider] = strategy;
}
}
64 changes: 64 additions & 0 deletions contracts/misc/LidoStakingStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {WadRayMath} from "../protocol/libraries/math/WadRayMath.sol";
import {IACLManager} from "../interfaces/IACLManager.sol";
import {IPoolAddressesProvider} from "../interfaces/IPoolAddressesProvider.sol";
import {IETHStakingProviderStrategy} from "../interfaces/IETHStakingProviderStrategy.sol";
import {PercentageMath} from "../protocol/libraries/math/PercentageMath.sol";

contract LidoStakingStrategy is IETHStakingProviderStrategy {
uint256 constant SLASHING_RISK =
PercentageMath.percentMul(WadRayMath.WAD, 50);
uint256 constant STAKING_RATE =
PercentageMath.percentMul(WadRayMath.WAD, 50);
uint256 constant DURATION_FACTOR =
PercentageMath.percentMul(WadRayMath.WAD, 10);
uint256 constant PROVIDER_PREMIUM =
PercentageMath.percentMul(WadRayMath.WAD, 15);

function getTokenPresentValue(TokenInfo tokenInfo, uint256 discountRate)
external
returns (uint256 price)
{
uint256 redemptionRate = getLidoRedemptionRate(
tokenInfo.underlyingTokenId
);

uint256 unstakeTime = (SLASHING_RISK *
tokenInfo.unstakeTime -
block.timestamp) / 86400;

return
(tokenInfo.principal *
redemptionRate *
(WadRayMath.WAD - SLASHING_RISK * unstakeTime)) /
(WadRayMath.WAD + discountRate)**unstakeTime;
}

function getDiscountRate(TokenInfo tokenInfo, uint256 borrowRate)
external
returns (uint256 discountRate)
{
uint256 unstakeTime = (SLASHING_RISK *
tokenInfo.unstakeTime -
block.timestamp) / 86400;

return borrowRate + borrowRate * DURATION_FACTOR * PROVIDER_PREMIUM;
}

function getSlashingRisk(uint256 tokenId)
external
returns (uint256 slashingRisk)
{
return SLASHING_RISK;
}

function getStakingRate(uint256 tokenId)
external
returns (uint256 stakingRate)
{
return STAKING_RATE;
}

function getLidoRedemptionRate(uint256 tokenId) internal returns (uint256) {
return WadRayMath.WAD;
}
}