From a929647701554fe5ca7bd721c2ad896c247cf7b1 Mon Sep 17 00:00:00 2001 From: 0xddong Date: Wed, 14 Aug 2024 20:51:43 -0700 Subject: [PATCH] submit offer before concentration check --- src/RepoTokenList.sol | 13 +++- src/Strategy.sol | 131 ++++++++++++++-------------------------- src/TermAuctionList.sol | 3 +- 3 files changed, 56 insertions(+), 91 deletions(-) diff --git a/src/RepoTokenList.sol b/src/RepoTokenList.sol index 3a78d1ca..9c1d339c 100644 --- a/src/RepoTokenList.sol +++ b/src/RepoTokenList.sol @@ -197,6 +197,14 @@ library RepoTokenList { address current = listData.head; while (current != NULL_NODE) { + // Filter by a specific repoToken, address(0) bypasses this filter + if (repoTokenToMatch != address(0) && current != repoTokenToMatch) { + // Not a match, do not add to totalPresentValue + // Move to the next token in the list + current = _getNext(listData, current); + continue; + } + uint256 currentMaturity = getRepoTokenMaturity(current); uint256 repoTokenBalance = ITermRepoToken(current).balanceOf(address(this)); uint256 repoTokenPrecision = 10**ERC20(current).decimals(); @@ -217,10 +225,9 @@ library RepoTokenList { totalPresentValue += repoTokenBalanceInBaseAssetPrecision; } - // If filtering by a specific repo token, stop early if matched + // Filter by a specific repo token, address(0) bypasses this condition if (repoTokenToMatch != address(0) && current == repoTokenToMatch) { - // matching a specific repoToken and terminate early because the list is sorted - // with no duplicates + // Found a match, terminate early break; } diff --git a/src/Strategy.sol b/src/Strategy.sol index c58673e9..7e5ef494 100644 --- a/src/Strategy.sol +++ b/src/Strategy.sol @@ -302,11 +302,12 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard { revert RepoTokenList.InvalidRepoToken(address(repoToken)); } - repoTokenListData.validateRepoToken( + uint256 redemptionTimestamp = repoTokenListData.validateRepoToken( ITermRepoToken(repoToken), address(asset) ); + uint256 discountRate = discountRateAdapter.getDiscountRate(repoToken); uint256 repoTokenPrecision = 10 ** ERC20(repoToken).decimals(); uint256 repoTokenAmountInBaseAssetPrecision = (ITermRepoToken( repoToken @@ -314,10 +315,17 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard { amount * PURCHASE_TOKEN_PRECISION) / (repoTokenPrecision * RepoTokenUtils.RATE_PRECISION); + uint256 proceeds = RepoTokenUtils.calculatePresentValue( + repoTokenAmountInBaseAssetPrecision, + PURCHASE_TOKEN_PRECISION, + redemptionTimestamp, + discountRate + discountRateMarkup + ); + _validateRepoTokenConcentration( repoToken, repoTokenAmountInBaseAssetPrecision, - 0 + proceeds ); } @@ -481,45 +489,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard { revert RepoTokenConcentrationTooHigh(repoToken); } } - - /** - * @notice Validates the resulting weighted time to maturity when submitting a new offer - * @param repoToken The address of the repoToken associated with the Term auction offer - * @param newOfferAmount The amount associated with the Term auction offer - * @param newLiquidBalance The new liquid balance of the strategy after accounting for the new offer - * - * @dev This function calculates the resulting weighted time to maturity assuming that a submitted offer is accepted - * and checks if it exceeds the predefined threshold (`timeToMaturityThreshold`). If the threshold is exceeded, - * the function reverts the transaction with an error. - */ - function _validateWeightedMaturity( - address repoToken, - uint256 newOfferAmount, - uint256 newLiquidBalance - ) private { - // Calculate the precision of the repoToken - uint256 repoTokenPrecision = 10 ** ERC20(repoToken).decimals(); - - // Convert the new offer amount to repoToken precision - uint256 offerAmountInRepoPrecision = RepoTokenUtils - .purchaseToRepoPrecision( - repoTokenPrecision, - PURCHASE_TOKEN_PRECISION, - newOfferAmount - ); - - // Calculate the resulting weighted time to maturity - uint256 resultingWeightedTimeToMaturity = _calculateWeightedMaturity( - repoToken, - offerAmountInRepoPrecision, - newLiquidBalance - ); - - // Check if the resulting weighted time to maturity exceeds the threshold - if (resultingWeightedTimeToMaturity > timeToMaturityThreshold) { - revert TimeToMaturityAboveThreshold(); - } - } /** * @notice Calculates the weighted time to maturity for the strategy's holdings, including the impact of a specified repoToken and amount @@ -686,8 +655,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard { address(asset) ); - _validateRepoTokenConcentration(repoToken, purchaseTokenAmount, 0); - // Prepare and submit the offer ITermAuctionOfferLocker offerLocker = ITermAuctionOfferLocker( termAuction.termAuctionOfferLocker() @@ -739,54 +706,12 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard { _sweepAsset(); _redeemRepoTokens(0); - // Retrieve the total liquid balance - uint256 liquidBalance = _totalLiquidBalance(address(this)); - - uint256 newOfferAmount = purchaseTokenAmount; bytes32 offerId = _generateOfferId(idHash, address(offerLocker)); + uint256 newOfferAmount = purchaseTokenAmount; uint256 currentOfferAmount = termAuctionListData .offers[offerId] .offerAmount; - // Handle adjustments if editing an existing offer - if (newOfferAmount > currentOfferAmount) { - // increasing offer amount - uint256 offerDebit; - unchecked { - // checked above - offerDebit = newOfferAmount - currentOfferAmount; - } - if (liquidBalance < offerDebit) { - revert InsufficientLiquidBalance(liquidBalance, offerDebit); - } - uint256 newLiquidBalance = liquidBalance - offerDebit; - if (newLiquidBalance < liquidityReserveRatio) { - revert BalanceBelowliquidityReserveRatio(); - } - _validateWeightedMaturity( - repoToken, - newOfferAmount, - newLiquidBalance - ); - } else if (currentOfferAmount > newOfferAmount) { - // decreasing offer amount - uint256 offerCredit; - unchecked { - offerCredit = currentOfferAmount - newOfferAmount; - } - uint256 newLiquidBalance = liquidBalance + offerCredit; - if (newLiquidBalance < liquidityReserveRatio) { - revert BalanceBelowliquidityReserveRatio(); - } - _validateWeightedMaturity( - repoToken, - newOfferAmount, - newLiquidBalance - ); - } else { - // no change in offer amount, do nothing - } - // Submit the offer and lock it in the auction ITermAuctionOfferLocker.TermAuctionOfferSubmission memory offer; offer.id = currentOfferAmount > 0 ? offerId : idHash; @@ -795,6 +720,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard { offer.amount = purchaseTokenAmount; offer.purchaseToken = address(asset); + // InsufficientLiquidBalance checked inside _submitOffer offerIds = _submitOffer( termAuction, offerLocker, @@ -803,6 +729,30 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard { newOfferAmount, currentOfferAmount ); + + // Retrieve the total liquid balance + uint256 liquidBalance = _totalLiquidBalance(address(this)); + + // Check that new offer does not violate reserve ratio constraint + if (liquidBalance < liquidityReserveRatio) { + revert BalanceBelowliquidityReserveRatio(); + } + + // Calculate the resulting weighted time to maturity + // Passing in 0 adjustment because offer and balance already updated + uint256 resultingWeightedTimeToMaturity = _calculateWeightedMaturity( + address(0), + 0, + liquidBalance + ); + + // Check if the resulting weighted time to maturity exceeds the threshold + if (resultingWeightedTimeToMaturity > timeToMaturityThreshold) { + revert TimeToMaturityAboveThreshold(); + } + + // Passing in 0 amount and 0 liquid balance adjustment because offer and balance already updated + _validateRepoTokenConcentration(repoToken, 0, 0); } /** @@ -842,6 +792,12 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard { // checked above offerDebit = newOfferAmount - currentOfferAmount; } + + uint256 liquidBalance = _totalLiquidBalance(address(this)); + if (liquidBalance < offerDebit) { + revert InsufficientLiquidBalance(liquidBalance, offerDebit); + } + _withdrawAsset(offerDebit); IERC20(asset).safeApprove( address(repoServicer.termRepoLocker()), @@ -1002,7 +958,8 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard { } // Ensure the remaining liquid balance is above the liquidity threshold - if ((liquidBalance - proceeds) < liquidityReserveRatio) { + uint256 newLiquidBalance = liquidBalance - proceeds; + if (newLiquidBalance < liquidityReserveRatio) { revert BalanceBelowliquidityReserveRatio(); } diff --git a/src/TermAuctionList.sol b/src/TermAuctionList.sol index 59b42d96..3176f593 100644 --- a/src/TermAuctionList.sol +++ b/src/TermAuctionList.sol @@ -277,8 +277,9 @@ library TermAuctionList { for (uint256 i; i < offers.length; i++) { PendingOfferMemory memory offer = offers[i]; - // Filter by specific repo token if provided + // Filter by specific repo token if provided, address(0) bypasses this filter if (repoTokenToMatch != address(0) && offer.repoToken != repoTokenToMatch) { + // Not a match, skip continue; }