Skip to content

Commit

Permalink
refactor calculations to use utils
Browse files Browse the repository at this point in the history
  • Loading branch information
aazhou1 committed Sep 10, 2024
1 parent 47bb445 commit 1e6e4d2
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 46 deletions.
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);
}
}
62 changes: 34 additions & 28 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,11 +428,10 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
function getRepoTokenHoldingValue(
address repoToken
) public view returns (uint256) {
uint256 repoTokenBalance = ITermRepoToken(repoToken).balanceOf(address(this));
uint256 repoTokenPresentValue = calculateRepoTokenPresentValue(repoToken, discountRateAdapter.getDiscountRate(repoToken), repoTokenBalance);
return
repoTokenListData.getPresentValue(
PURCHASE_TOKEN_PRECISION,
repoToken
) +
repoTokenPresentValue +
termAuctionListData.getPresentValue(
repoTokenListData,
discountRateAdapter,
Expand Down Expand Up @@ -489,6 +488,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
return
liquidBalance +
repoTokenListData.getPresentValue(
discountRateAdapter,
PURCHASE_TOKEN_PRECISION,
address(0)
) +
Expand Down Expand Up @@ -596,6 +596,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
uint256 cumulativeRepoTokenAmount,
bool foundInRepoTokenList
) = repoTokenListData.getCumulativeRepoTokenData(
discountRateAdapter,
repoToken,
repoTokenAmount,
PURCHASE_TOKEN_PRECISION
Expand All @@ -611,6 +612,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
bool foundInOfferList
) = termAuctionListData.getCumulativeOfferData(
repoTokenListData,
discountRateAdapter,
repoToken,
repoTokenAmount,
PURCHASE_TOKEN_PRECISION
Expand All @@ -625,11 +627,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 +1009,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 +1024,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

0 comments on commit 1e6e4d2

Please sign in to comment.