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

Refactor to use repo token utils #21

Merged
merged 4 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
20 changes: 11 additions & 9 deletions src/RepoTokenList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ library RepoTokenList {
/**
* @notice This function calculates the cumulative weighted time to maturity and cumulative amount of all repoTokens in the list.
* @param listData The list data
* @param discountRateAdapter The discount rate adapter
* @param repoToken The address of the repoToken (optional)
* @param repoTokenAmount The amount of the repoToken (optional)
* @param purchaseTokenPrecision The precision of the purchase token
Expand All @@ -127,6 +128,7 @@ library RepoTokenList {
*/
function getCumulativeRepoTokenData(
RepoTokenListData storage listData,
ITermDiscountRateAdapter discountRateAdapter,
address repoToken,
uint256 repoTokenAmount,
uint256 purchaseTokenPrecision
Expand All @@ -148,12 +150,10 @@ library RepoTokenList {
}

// Convert the repo token balance to base asset precision
uint256 redemptionValue = ITermRepoToken(current).redemptionValue();
uint256 repoTokenPrecision = 10**ERC20(current).decimals();

uint256 repoTokenBalanceInBaseAssetPrecision =
(redemptionValue * repoTokenBalance * purchaseTokenPrecision) /
(repoTokenPrecision * RepoTokenUtils.RATE_PRECISION);
RepoTokenUtils.getNormalizedRepoTokenAmount(
current, repoTokenBalance, purchaseTokenPrecision, discountRateAdapter.repoRedemptionHaircut(current)
);

// Calculate the weighted time to maturity
uint256 weightedTimeToMaturity = getRepoTokenWeightedTimeToMaturity(
Expand All @@ -173,6 +173,7 @@ library RepoTokenList {
/**
* @notice Get the present value of repoTokens
* @param listData The list data
* @param discountRateAdapter The discount rate adapter
* @param purchaseTokenPrecision The precision of the purchase token
* @param repoTokenToMatch The address of the repoToken to match (optional)
* @return totalPresentValue The total present value of the repoTokens
Expand All @@ -186,6 +187,7 @@ library RepoTokenList {
*/
function getPresentValue(
RepoTokenListData storage listData,
ITermDiscountRateAdapter discountRateAdapter,
uint256 purchaseTokenPrecision,
address repoTokenToMatch
) internal view returns (uint256 totalPresentValue) {
Expand All @@ -204,14 +206,14 @@ library RepoTokenList {

uint256 currentMaturity = getRepoTokenMaturity(current);
uint256 repoTokenBalance = ITermRepoToken(current).balanceOf(address(this));
uint256 repoTokenPrecision = 10**ERC20(current).decimals();
uint256 discountRate = listData.discountRates[current];
uint256 discountRate = discountRateAdapter.getDiscountRate(current);

// Convert repo token balance to base asset precision
// (ratePrecision * repoPrecision * purchasePrecision) / (repoPrecision * ratePrecision) = purchasePrecision
uint256 repoTokenBalanceInBaseAssetPrecision =
(ITermRepoToken(current).redemptionValue() * repoTokenBalance * purchaseTokenPrecision) /
(repoTokenPrecision * RepoTokenUtils.RATE_PRECISION);
RepoTokenUtils.getNormalizedRepoTokenAmount(
current, repoTokenBalance, purchaseTokenPrecision, discountRateAdapter.repoRedemptionHaircut(current)
);

// Calculate present value based on maturity
if (currentMaturity > block.timestamp) {
Expand Down
8 changes: 5 additions & 3 deletions src/RepoTokenUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,19 @@ library RepoTokenUtils {
* @param repoToken The address of the repoToken
* @param repoTokenAmount The amount of the repoToken
* @param purchaseTokenPrecision The precision of the purchase token
* @param repoRedemptionHaircut The haircut to be applied to the repoToken for bad debt
* @return repoTokenAmountInBaseAssetPrecision The normalized amount of the repoToken in base asset precision
*/
function getNormalizedRepoTokenAmount(
address repoToken,
uint256 repoTokenAmount,
uint256 purchaseTokenPrecision
uint256 purchaseTokenPrecision,
uint256 repoRedemptionHaircut
) internal view returns (uint256 repoTokenAmountInBaseAssetPrecision) {
uint256 repoTokenPrecision = 10**ERC20(repoToken).decimals();
uint256 redemptionValue = ITermRepoToken(repoToken).redemptionValue();
repoTokenAmountInBaseAssetPrecision =
(redemptionValue * repoTokenAmount * purchaseTokenPrecision) /
(repoTokenPrecision * RepoTokenUtils.RATE_PRECISION);
(redemptionValue * repoRedemptionHaircut * repoTokenAmount * purchaseTokenPrecision) /
(repoTokenPrecision * RATE_PRECISION * 1e18);
}
}
58 changes: 33 additions & 25 deletions src/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -351,15 +351,14 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
ITermRepoToken(repoToken),
address(asset)
);

uint256 discountRate = discountRateAdapter.getDiscountRate(repoToken);
uint256 repoTokenPrecision = 10 ** ERC20(repoToken).decimals();
repoTokenAmountInBaseAssetPrecision = (ITermRepoToken(
repoToken
).redemptionValue() *
amount *
PURCHASE_TOKEN_PRECISION) /
(repoTokenPrecision * RepoTokenUtils.RATE_PRECISION);
repoTokenAmountInBaseAssetPrecision = RepoTokenUtils.getNormalizedRepoTokenAmount(
repoToken,
amount,
PURCHASE_TOKEN_PRECISION,
discountRateAdapter.repoRedemptionHaircut(repoToken)
);
proceeds = RepoTokenUtils.calculatePresentValue(
repoTokenAmountInBaseAssetPrecision,
PURCHASE_TOKEN_PRECISION,
Expand Down Expand Up @@ -398,15 +397,16 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
address repoToken,
uint256 discountRate,
uint256 amount
) external view returns (uint256) {
) public view returns (uint256) {
(uint256 redemptionTimestamp, , , ) = ITermRepoToken(repoToken)
.config();
uint256 repoTokenPrecision = 10 ** ERC20(repoToken).decimals();
uint256 repoTokenAmountInBaseAssetPrecision = (ITermRepoToken(repoToken)
.redemptionValue() *
amount *
PURCHASE_TOKEN_PRECISION) /
(repoTokenPrecision * RepoTokenUtils.RATE_PRECISION);
uint256 repoTokenAmountInBaseAssetPrecision = RepoTokenUtils
.getNormalizedRepoTokenAmount(
repoToken,
amount,
PURCHASE_TOKEN_PRECISION,
discountRateAdapter.repoRedemptionHaircut(repoToken)
);
return
RepoTokenUtils.calculatePresentValue(
repoTokenAmountInBaseAssetPrecision,
Expand All @@ -428,8 +428,9 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
function getRepoTokenHoldingValue(
address repoToken
) public view returns (uint256) {
return
return
repoTokenListData.getPresentValue(
discountRateAdapter,
PURCHASE_TOKEN_PRECISION,
repoToken
) +
Expand Down Expand Up @@ -489,6 +490,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
return
liquidBalance +
repoTokenListData.getPresentValue(
discountRateAdapter,
PURCHASE_TOKEN_PRECISION,
address(0)
) +
Expand Down Expand Up @@ -596,6 +598,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
uint256 cumulativeRepoTokenAmount,
bool foundInRepoTokenList
) = repoTokenListData.getCumulativeRepoTokenData(
discountRateAdapter,
repoToken,
repoTokenAmount,
PURCHASE_TOKEN_PRECISION
Expand All @@ -611,6 +614,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
bool foundInOfferList
) = termAuctionListData.getCumulativeOfferData(
repoTokenListData,
discountRateAdapter,
repoToken,
repoTokenAmount,
PURCHASE_TOKEN_PRECISION
Expand All @@ -625,11 +629,13 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
!foundInOfferList &&
repoToken != address(0)
) {
uint256 repoRedemptionHaircut = discountRateAdapter.repoRedemptionHaircut(repoToken);
uint256 repoTokenAmountInBaseAssetPrecision = RepoTokenUtils
.getNormalizedRepoTokenAmount(
repoToken,
repoTokenAmount,
PURCHASE_TOKEN_PRECISION
PURCHASE_TOKEN_PRECISION,
repoRedemptionHaircut
);

cumulativeAmount += repoTokenAmountInBaseAssetPrecision;
Expand Down Expand Up @@ -1005,7 +1011,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
}

// Validate and insert the repoToken into the list, retrieve auction rate and redemption timestamp
(uint256 discountRate, uint256 redemptionTimestamp) = repoTokenListData
(, uint256 redemptionTimestamp) = repoTokenListData
.validateAndInsertRepoToken(
ITermRepoToken(repoToken),
discountRateAdapter,
Expand All @@ -1020,13 +1026,15 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
uint256 liquidBalance = _totalLiquidBalance();
require(liquidBalance > 0);

// Calculate the repoToken amount in base asset precision
uint256 repoTokenPrecision = 10 ** ERC20(repoToken).decimals();
uint256 repoTokenAmountInBaseAssetPrecision = (ITermRepoToken(repoToken)
.redemptionValue() *
repoTokenAmount *
PURCHASE_TOKEN_PRECISION) /
(repoTokenPrecision * RepoTokenUtils.RATE_PRECISION);
uint256 discountRate = discountRateAdapter.getDiscountRate(repoToken);

// Calculate the repoToken amount in base asset precision
uint256 repoTokenAmountInBaseAssetPrecision = RepoTokenUtils.getNormalizedRepoTokenAmount(
repoToken,
repoTokenAmount,
PURCHASE_TOKEN_PRECISION,
discountRateAdapter.repoRedemptionHaircut(repoToken)
);

// Calculate the proceeds from selling the repoToken
uint256 proceeds = RepoTokenUtils.calculatePresentValue(
Expand Down
8 changes: 6 additions & 2 deletions src/TermAuctionList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ library TermAuctionList {
uint256 repoTokenAmountInBaseAssetPrecision = RepoTokenUtils.getNormalizedRepoTokenAmount(
offer.repoToken,
ITermRepoToken(offer.repoToken).balanceOf(address(this)),
purchaseTokenPrecision
purchaseTokenPrecision,
discountRateAdapter.repoRedemptionHaircut(offer.repoToken)
);
totalValue += RepoTokenUtils.calculatePresentValue(
repoTokenAmountInBaseAssetPrecision,
Expand All @@ -316,6 +317,7 @@ library TermAuctionList {
* @notice Get cumulative offer data for a specified repoToken
* @param listData The list data
* @param repoTokenListData The repoToken list data
* @param discountRateAdapter The discount rate adapter
* @param repoToken The address of the repoToken (optional)
* @param newOfferAmount The new offer amount for the specified repoToken
* @param purchaseTokenPrecision The precision of the purchase token
Expand All @@ -331,6 +333,7 @@ library TermAuctionList {
function getCumulativeOfferData(
TermAuctionListData storage listData,
RepoTokenListData storage repoTokenListData,
ITermDiscountRateAdapter discountRateAdapter,
address repoToken,
uint256 newOfferAmount,
uint256 purchaseTokenPrecision
Expand Down Expand Up @@ -361,7 +364,8 @@ library TermAuctionList {
offerAmount = RepoTokenUtils.getNormalizedRepoTokenAmount(
offer.repoToken,
ITermRepoToken(offer.repoToken).balanceOf(address(this)),
purchaseTokenPrecision
purchaseTokenPrecision,
discountRateAdapter.repoRedemptionHaircut(offer.repoToken)
);

_markRepoTokenAsSeen(offers, offer.repoToken);
Expand Down
21 changes: 18 additions & 3 deletions src/TermDiscountRateAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,28 @@ pragma solidity ^0.8.18;
import {ITermDiscountRateAdapter} from "./interfaces/term/ITermDiscountRateAdapter.sol";
import {ITermController, AuctionMetadata} from "./interfaces/term/ITermController.sol";
import {ITermRepoToken} from "./interfaces/term/ITermRepoToken.sol";
import "@openzeppelin/contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol";

/**
* @title TermDiscountRateAdapter
* @notice Adapter contract to retrieve discount rates for Term repo tokens
* @dev This contract implements the ITermDiscountRateAdapter interface and interacts with the Term Controller
*/
contract TermDiscountRateAdapter is ITermDiscountRateAdapter {
contract TermDiscountRateAdapter is ITermDiscountRateAdapter, AccessControlUpgradeable {

bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
/// @notice The Term Controller contract
ITermController public immutable TERM_CONTROLLER;
mapping(address => uint256) public repoRedemptionHaircut;

/**
* @notice Constructor to initialize the TermDiscountRateAdapter
* @param termController_ The address of the Term Controller contract
* @param oracleWallet_ The address of the oracle wallet
*/
constructor(address termController_) {
constructor(address termController_, address oracleWallet_) {
TERM_CONTROLLER = ITermController(termController_);
_grantRole(ORACLE_ROLE, oracleWallet_);
}

/**
Expand All @@ -37,4 +43,13 @@ contract TermDiscountRateAdapter is ITermDiscountRateAdapter {

return auctionMetadata[len - 1].auctionClearingRate;
}
}

/**
* @notice Set the repo redemption haircut
* @param repoToken The address of the repo token
* @param haircut The repo redemption haircut in 18 decimals
*/
function setRepoRedemptionHaircut(address repoToken, uint256 haircut) external onlyRole(ORACLE_ROLE) {
repoRedemptionHaircut[repoToken] = haircut;
}
}
1 change: 1 addition & 0 deletions src/interfaces/term/ITermDiscountRateAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
pragma solidity ^0.8.18;

interface ITermDiscountRateAdapter {
function repoRedemptionHaircut(address) external view returns (uint256);
function getDiscountRate(address repoToken) external view returns (uint256);
}
2 changes: 1 addition & 1 deletion src/test/utils/Setup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ contract Setup is ExtendedTest, IEvents {
vm.etch(0xBB51273D6c746910C7C06fe718f30c936170feD0, address(tokenizedStrategy).code);

termController = new MockTermController();
discountRateAdapter = new TermDiscountRateAdapter(address(termController));
discountRateAdapter = new TermDiscountRateAdapter(address(termController), adminWallet);
termVaultEventEmitterImpl = new TermVaultEventEmitter();
termVaultEventEmitter = TermVaultEventEmitter(address(new ERC1967Proxy(address(termVaultEventEmitterImpl), "")));
mockYearnVault = new ERC4626Mock(address(asset));
Expand Down
Loading