Skip to content

Commit

Permalink
Merge pull request #10 from term-finance/fixing-strategy-tests
Browse files Browse the repository at this point in the history
Fixing strategy tests
  • Loading branch information
0xddong authored Aug 16, 2024
2 parents d5f6a33 + 3037b95 commit 7b61bba
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 69 deletions.
132 changes: 82 additions & 50 deletions src/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
// Errors
error InvalidTermAuction(address auction);
error TimeToMaturityAboveThreshold();
error BalanceBelowliquidityReserveRatio();
error BalanceBelowRequiredReserveRatio();
error InsufficientLiquidBalance(uint256 have, uint256 want);
error RepoTokenConcentrationTooHigh(address repoToken);
error RepoTokenBlacklisted(address repoToken);
Expand All @@ -62,9 +62,9 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
RepoTokenListData internal repoTokenListData;
TermAuctionListData internal termAuctionListData;
uint256 public timeToMaturityThreshold; // seconds
uint256 public liquidityReserveRatio; // purchase token precision (underlying)
uint256 public requiredReserveRatio; // 1e18
uint256 public discountRateMarkup; // 1e18 (TODO: check this)
uint256 public repoTokenConcentrationLimit;
uint256 public repoTokenConcentrationLimit; // 1e18
mapping(address => bool) repoTokenBlacklist;
bool depositLock;

Expand Down Expand Up @@ -172,16 +172,16 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {

/**
* @notice Set the liquidity reserve factor
* @param newLiquidityReserveRatio The new liquidity reserve factor
* @param newRequiredReserveRatio The new liquidity reserve factor
*/
function setLiquidityReserveRatio(
uint256 newLiquidityReserveRatio
function setRequiredReserveRatio(
uint256 newRequiredReserveRatio
) external onlyManagement {
TERM_VAULT_EVENT_EMITTER.emitLiquidityReserveRatioUpdated(
liquidityReserveRatio,
newLiquidityReserveRatio
TERM_VAULT_EVENT_EMITTER.emitRequiredReserveRatioUpdated(
requiredReserveRatio,
newRequiredReserveRatio
);
liquidityReserveRatio = newLiquidityReserveRatio;
requiredReserveRatio = newRequiredReserveRatio;
}

/**
Expand Down Expand Up @@ -237,7 +237,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/


/**
* @notice Calculates the total value of all assets managed by the strategy
* @return The total asset value in the purchase token precision
Expand All @@ -246,7 +245,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
* and the present value of all pending offers to calculate the total asset value.
*/
function totalAssetValue() external view returns (uint256) {
return _totalAssetValue();
return _totalAssetValue(_totalLiquidBalance(address(this)));
}

/**
Expand All @@ -260,6 +259,16 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
return _totalLiquidBalance(address(this));
}

function _liquidReserveRatio(uint256 liquidBalance) internal view returns (uint256) {
uint256 assetValue = _totalAssetValue(liquidBalance);
if (assetValue == 0) return 0;
return liquidBalance * 1e18 / assetValue;
}

function liquidReserveRatio() external view returns (uint256) {
return _liquidReserveRatio(_totalLiquidBalance(address(this)));
}

/**
* @notice Returns an array of addresses representing the repoTokens currently held by the strategy
* @return address[] An array of addresses of the repoTokens held by the strategy
Expand All @@ -282,21 +291,36 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
return termAuctionListData.pendingOffers();
}

function getRepoTokenConcentrationRatio(address repoToken) external view returns (uint256) {
if (repoToken == address(0)) {
revert RepoTokenList.InvalidRepoToken(address(0));
}
return _getRepoTokenConcentrationRatio(
repoToken, 0, _totalAssetValue(_totalLiquidBalance(address(0))), 0
);
}

/**
* @notice Simulates the weighted time to maturity for a specified repoToken and amount, including the impact on the entire strategy's holdings
* @param repoToken The address of the repoToken to be simulated
* @param amount The amount of the repoToken to be simulated
* @return uint256 The simulated weighted time to maturity for the entire strategy
* @return simulatedWeightedMaturity The simulated weighted time to maturity for the entire strategy
*
* @dev This function validates the repoToken, normalizes its amount, checks concentration limits,
* and calculates the weighted time to maturity for the specified repoToken and amount. The result
* reflects the new weighted time to maturity for the entire strategy, including the new repoToken position.
*/
function simulateWeightedTimeToMaturity(
function simulateTransaction(
address repoToken,
uint256 amount
) external view returns (uint256) {
) external view returns (
uint256 simulatedWeightedMaturity,
uint256 simulatedLiquidityRatio
) {
// do not validate if we are simulating with existing repoTokens
uint256 liquidBalance = _totalLiquidBalance(address(0));
uint256 repoTokenAmountInBaseAssetPrecision;
uint256 proceeds;
if (repoToken != address(0)) {
if (!_isTermDeployed(repoToken)) {
revert RepoTokenList.InvalidRepoToken(address(repoToken));
Expand All @@ -309,32 +333,24 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {

uint256 discountRate = discountRateAdapter.getDiscountRate(repoToken);
uint256 repoTokenPrecision = 10 ** ERC20(repoToken).decimals();
uint256 repoTokenAmountInBaseAssetPrecision = (ITermRepoToken(
repoTokenAmountInBaseAssetPrecision = (ITermRepoToken(
repoToken
).redemptionValue() *
amount *
PURCHASE_TOKEN_PRECISION) /
(repoTokenPrecision * RepoTokenUtils.RATE_PRECISION);
uint256 proceeds = RepoTokenUtils.calculatePresentValue(
proceeds = RepoTokenUtils.calculatePresentValue(
repoTokenAmountInBaseAssetPrecision,
PURCHASE_TOKEN_PRECISION,
redemptionTimestamp,
discountRate + discountRateMarkup
);

_validateRepoTokenConcentration(
repoToken,
repoTokenAmountInBaseAssetPrecision,
proceeds
);
}

return
_calculateWeightedMaturity(
repoToken,
amount,
_totalLiquidBalance(address(this))
);
simulatedWeightedMaturity = _calculateWeightedMaturity(
repoToken, amount, liquidBalance - proceeds);

simulatedLiquidityRatio = _liquidReserveRatio(liquidBalance - proceeds);
}

/**
Expand Down Expand Up @@ -440,9 +456,9 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
* @dev This function aggregates the total liquid balance, the present value of all repoTokens,
* and the present value of all pending offers to calculate the total asset value.
*/
function _totalAssetValue() internal view returns (uint256 totalValue) {
function _totalAssetValue(uint256 liquidBalance) internal view returns (uint256 totalValue) {
return
_totalLiquidBalance(address(this)) +
liquidBalance +
repoTokenListData.getPresentValue(
PURCHASE_TOKEN_PRECISION,
address(0)
Expand All @@ -455,34 +471,49 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
);
}

/**
* @dev Validate the concentration of repoTokens
* @param repoToken The address of the repoToken
* @param repoTokenAmountInBaseAssetPrecision The amount of the repoToken in base asset precision
* @param liquidBalanceToRemove The liquid balance to remove
*/
function _validateRepoTokenConcentration(
function _getRepoTokenConcentrationRatio(
address repoToken,
uint256 repoTokenAmountInBaseAssetPrecision,
uint256 liquidBalanceToRemove
) private view {
uint256 assetValue,
uint256 liquidBalanceToRemove
) private view returns (uint256) {
// Retrieve the current value of the repoToken held by the strategy and add the new repoToken amount
uint256 repoTokenValue = getRepoTokenHoldingValue(repoToken) +
repoTokenAmountInBaseAssetPrecision;

// Retrieve the total asset value of the strategy and adjust it for the new repoToken amount and liquid balance to be removed
uint256 totalAsseValue = _totalAssetValue() +
uint256 totalAssetValue = assetValue +
repoTokenAmountInBaseAssetPrecision -
liquidBalanceToRemove;

// Normalize the repoToken value and total asset value to 1e18 precision
repoTokenValue = (repoTokenValue * 1e18) / PURCHASE_TOKEN_PRECISION;
totalAsseValue = (totalAsseValue * 1e18) / PURCHASE_TOKEN_PRECISION;
totalAssetValue = (totalAssetValue * 1e18) / PURCHASE_TOKEN_PRECISION;

// Calculate the repoToken concentration
uint256 repoTokenConcentration = totalAsseValue == 0
return totalAssetValue == 0
? 0
: (repoTokenValue * 1e18) / totalAsseValue;
: (repoTokenValue * 1e18) / totalAssetValue;
}

/**
* @dev Validate the concentration of repoTokens
* @param repoToken The address of the repoToken
* @param repoTokenAmountInBaseAssetPrecision The amount of the repoToken in base asset precision
* @param liquidBalanceToRemove The liquid balance to remove
*/
function _validateRepoTokenConcentration(
address repoToken,
uint256 repoTokenAmountInBaseAssetPrecision,
uint256 assetValue,
uint256 liquidBalanceToRemove
) private view {
uint256 repoTokenConcentration = _getRepoTokenConcentrationRatio(
repoToken,
repoTokenAmountInBaseAssetPrecision,
assetValue,
liquidBalanceToRemove
);

// Check if the repoToken concentration exceeds the predefined limit
if (repoTokenConcentration > repoTokenConcentrationLimit) {
Expand Down Expand Up @@ -734,8 +765,8 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
uint256 liquidBalance = _totalLiquidBalance(address(this));

// Check that new offer does not violate reserve ratio constraint
if (liquidBalance < liquidityReserveRatio) {
revert BalanceBelowliquidityReserveRatio();
if (_liquidReserveRatio(liquidBalance) < requiredReserveRatio) {
revert BalanceBelowRequiredReserveRatio();
}

// Calculate the resulting weighted time to maturity
Expand All @@ -752,7 +783,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
}

// Passing in 0 amount and 0 liquid balance adjustment because offer and balance already updated
_validateRepoTokenConcentration(repoToken, 0, 0);
_validateRepoTokenConcentration(repoToken, 0, _totalAssetValue(liquidBalance), 0);
}

/**
Expand Down Expand Up @@ -959,14 +990,15 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {

// Ensure the remaining liquid balance is above the liquidity threshold
uint256 newLiquidBalance = liquidBalance - proceeds;
if (newLiquidBalance < liquidityReserveRatio) {
revert BalanceBelowliquidityReserveRatio();
if (_liquidReserveRatio(newLiquidBalance) < requiredReserveRatio) {
revert BalanceBelowRequiredReserveRatio();
}

// Validate resulting repoToken concentration to ensure it meets requirements
_validateRepoTokenConcentration(
repoToken,
repoTokenAmountInBaseAssetPrecision,
_totalAssetValue(liquidBalance),
proceeds
);

Expand Down Expand Up @@ -1087,7 +1119,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
{
_sweepAsset();
_redeemRepoTokens(0);
return _totalAssetValue();
return _totalAssetValue(_totalLiquidBalance(address(this)));
}

/*//////////////////////////////////////////////////////////////
Expand Down
4 changes: 2 additions & 2 deletions src/TermVaultEventEmitter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ contract TermVaultEventEmitter is Initializable, UUPSUpgradeable, AccessControlU
emit TimeToMaturityThresholdUpdated(oldThreshold, newThreshold);
}

function emitLiquidityReserveRatioUpdated(uint256 oldThreshold, uint256 newThreshold) external onlyRole(VAULT_CONTRACT) {
emit LiquidityReserveRatioUpdated(oldThreshold, newThreshold);
function emitRequiredReserveRatioUpdated(uint256 oldThreshold, uint256 newThreshold) external onlyRole(VAULT_CONTRACT) {
emit RequiredReserveRatioUpdated(oldThreshold, newThreshold);
}

function emitDiscountRateMarkupUpdated(uint256 oldMarkup, uint256 newMarkup) external onlyRole(VAULT_CONTRACT) {
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/term/ITermVaultEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface ITermVaultEvents {

event TimeToMaturityThresholdUpdated(uint256 oldThreshold, uint256 newThreshold);

event LiquidityReserveRatioUpdated(uint256 oldThreshold, uint256 newThreshold);
event RequiredReserveRatioUpdated(uint256 oldThreshold, uint256 newThreshold);

event DiscountRateMarkupUpdated(uint256 oldMarkup, uint256 newMarkup);

Expand Down Expand Up @@ -36,7 +36,7 @@ interface ITermVaultEvents {

function emitTimeToMaturityThresholdUpdated(uint256 oldThreshold, uint256 newThreshold) external;

function emitLiquidityReserveRatioUpdated(uint256 oldThreshold, uint256 newThreshold) external;
function emitRequiredReserveRatioUpdated(uint256 oldThreshold, uint256 newThreshold) external;

function emitDiscountRateMarkupUpdated(uint256 oldMarkup, uint256 newMarkup) external;

Expand Down
Loading

0 comments on commit 7b61bba

Please sign in to comment.