Skip to content

Commit

Permalink
various strategy fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
0xddong committed Aug 15, 2024
1 parent f7e6a82 commit 4eef899
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 74 deletions.
11 changes: 1 addition & 10 deletions src/RepoTokenList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ library RepoTokenList {
* @notice Validates a repoToken against specific criteria
* @param listData The list data
* @param repoToken The repoToken to validate
* @param termController The term controller
* @param asset The address of the base asset
* @return redemptionTimestamp The redemption timestamp of the validated repoToken
*
Expand All @@ -314,14 +313,8 @@ library RepoTokenList {
function validateRepoToken(
RepoTokenListData storage listData,
ITermRepoToken repoToken,
ITermController termController,
address asset
) internal view returns (uint256 redemptionTimestamp) {
// Ensure the repo token is deployed by term
if (!termController.isTermDeployed(address(repoToken))) {
revert InvalidRepoToken(address(repoToken));
}

// Retrieve repo token configuration
address purchaseToken;
address collateralManager;
Expand Down Expand Up @@ -357,7 +350,6 @@ library RepoTokenList {
* @notice Validate and insert a repoToken into the list data
* @param listData The list data
* @param repoToken The repoToken to validate and insert
* @param termController The term controller
* @param discountRateAdapter The discount rate adapter
* @param asset The address of the base asset
* @return discountRate The discount rate to be applied to the validated repoToken
Expand All @@ -366,7 +358,6 @@ library RepoTokenList {
function validateAndInsertRepoToken(
RepoTokenListData storage listData,
ITermRepoToken repoToken,
ITermController termController,
ITermDiscountRateAdapter discountRateAdapter,
address asset
) internal returns (uint256 discountRate, uint256 redemptionTimestamp) {
Expand All @@ -388,7 +379,7 @@ library RepoTokenList {
} else {
discountRate = discountRateAdapter.getDiscountRate(address(repoToken));

redemptionTimestamp = validateRepoToken(listData, repoToken, termController, asset);
redemptionTimestamp = validateRepoToken(listData, repoToken, asset);

insertSorted(listData, address(repoToken));
listData.discountRates[address(repoToken)] = discountRate;
Expand Down
153 changes: 112 additions & 41 deletions src/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,35 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
error BalanceBelowliquidityReserveRatio();
error InsufficientLiquidBalance(uint256 have, uint256 want);
error RepoTokenConcentrationTooHigh(address repoToken);
error RepoTokenBlacklisted(address repoToken);
error DepositPaused();

// Immutable state variables
ITermVaultEvents public immutable TERM_VAULT_EVENT_EMITTER;
uint256 public immutable PURCHASE_TOKEN_PRECISION;
IERC4626 public immutable YEARN_VAULT;

// State variables
ITermController public termController;
/// @notice previous term controller
ITermController public prevTermController;
/// @notice current term controller
ITermController public currTermController;
ITermDiscountRateAdapter public discountRateAdapter;
RepoTokenListData internal repoTokenListData;
TermAuctionListData internal termAuctionListData;
uint256 public timeToMaturityThreshold; // seconds
uint256 public liquidityReserveRatio; // purchase token precision (underlying)
uint256 public discountRateMarkup; // 1e18 (TODO: check this)
uint256 public repoTokenConcentrationLimit;
mapping(address => bool) repoTokenBlacklist;
bool depositLock;

modifier notBlacklisted(address repoToken) {
if (repoTokenBlacklist[repoToken]) {
revert RepoTokenBlacklisted(repoToken);
}
_;
}

/*//////////////////////////////////////////////////////////////
MANAGEMENT FUNCTIONS
Expand All @@ -82,17 +96,33 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
/**
* @notice Pause the contract
*/
function pause() external onlyManagement {
function pauseDeposit() external onlyManagement {
depositLock = true;
TERM_VAULT_EVENT_EMITTER.emitDepositPaused();
}

/**
* @notice Unpause the contract
*/
function unpauseDeposit() external onlyManagement {
depositLock = false;
TERM_VAULT_EVENT_EMITTER.emitDepositUnpaused();
}

/**
* @notice Pause the contract
*/
function pauseStrategy() external onlyManagement {
_pause();
TERM_VAULT_EVENT_EMITTER.emitPaused();
TERM_VAULT_EVENT_EMITTER.emitStrategyPaused();
}

/**
* @notice Unpause the contract
*/
function unpause() external onlyManagement {
function unpauseStrategy() external onlyManagement {
_unpause();
TERM_VAULT_EVENT_EMITTER.emitUnpaused();
TERM_VAULT_EVENT_EMITTER.emitStrategyUnpaused();
}

/**
Expand All @@ -103,11 +133,13 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
address newTermController
) external onlyManagement {
require(newTermController != address(0));
address current = address(currTermController);
TERM_VAULT_EVENT_EMITTER.emitTermControllerUpdated(
address(termController),
current,
newTermController
);
termController = ITermController(newTermController);
prevTermController = ITermController(current);
currTermController = ITermController(newTermController);
}

/**
Expand Down Expand Up @@ -196,6 +228,11 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
repoTokenListData.collateralTokenParams[tokenAddr] = minCollateralRatio;
}

function setRepoTokenBlacklist(address repoToken, bool blacklisted) external onlyManagement {
TERM_VAULT_EVENT_EMITTER.emitRepoTokenBlacklistUpdated(repoToken, blacklisted);
repoTokenBlacklist[repoToken] = blacklisted;
}

/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -261,9 +298,12 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
) external view returns (uint256) {
// do not validate if we are simulating with existing repoTokens
if (repoToken != address(0)) {
if (!_isTermDeployed(repoToken)) {
revert RepoTokenList.InvalidRepoToken(address(repoToken));
}

repoTokenListData.validateRepoToken(
ITermRepoToken(repoToken),
termController,
address(asset)
);

Expand Down Expand Up @@ -526,7 +566,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
bool foundInOfferList
) = termAuctionListData.getCumulativeOfferData(
repoTokenListData,
termController,
repoToken,
repoTokenAmount,
PURCHASE_TOKEN_PRECISION
Expand Down Expand Up @@ -572,6 +611,16 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
YEARN_VAULT.deposit(IERC20(asset).balanceOf(address(this)), address(this));
}

function _isTermDeployed(address termContract) private view returns (bool) {
if (address(currTermController) != address(0) && currTermController.isTermDeployed(termContract)) {
return true;
}
if (address(prevTermController) != address(0) && prevTermController.isTermDeployed(termContract)) {
return true;
}
return false;
}

/**
* @notice Rebalances the strategy's assets by sweeping assets and redeeming matured repoTokens
* @param liquidAmountRequired The amount of liquid assets required to be maintained by the strategy
Expand All @@ -586,7 +635,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
// Remove completed auction offers
termAuctionListData.removeCompleted(
repoTokenListData,
termController,
discountRateAdapter,
address(asset)
);
Expand Down Expand Up @@ -617,6 +665,42 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
STRATEGIST FUNCTIONS
//////////////////////////////////////////////////////////////*/

function _validateAndGetOfferLocker(
ITermAuction termAuction,
address repoToken,
uint256 purchaseTokenAmount
) private view returns (ITermAuctionOfferLocker) {
// Verify that the term auction and repo token are valid and deployed by term
if (!_isTermDeployed(address(termAuction))) {
revert InvalidTermAuction(address(termAuction));
}
if (!_isTermDeployed(repoToken)) {
revert RepoTokenList.InvalidRepoToken(address(repoToken));
}

require(termAuction.termRepoId() == ITermRepoToken(repoToken).termRepoId(), "repoToken does not match term repo ID");

// Validate purchase token, min collateral ratio and insert the repoToken if necessary
repoTokenListData.validateRepoToken(
ITermRepoToken(repoToken),
address(asset)
);

_validateRepoTokenConcentration(repoToken, purchaseTokenAmount, 0);

// Prepare and submit the offer
ITermAuctionOfferLocker offerLocker = ITermAuctionOfferLocker(
termAuction.termAuctionOfferLocker()
);
require(
block.timestamp > offerLocker.auctionStartTime() ||
block.timestamp < termAuction.auctionEndTime(),
"Auction not open"
);

return offerLocker;
}

/**
* @notice Submits an offer into a term auction for a specified repoToken
* @param termAuction The address of the term auction
Expand All @@ -630,7 +714,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
* and rebalances liquidity to support the offer submission. It handles both new offers and edits to existing offers.
*/
function submitAuctionOffer(
address termAuction,
ITermAuction termAuction,
address repoToken,
bytes32 idHash,
bytes32 offerPriceHash,
Expand All @@ -639,36 +723,16 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
external
whenNotPaused
nonReentrant
notBlacklisted(repoToken)
onlyManagement
returns (bytes32[] memory offerIds)
{
require(purchaseTokenAmount > 0, "Purchase token amount must be greater than zero");

// Verify that the term auction is valid and deployed by term
if (!termController.isTermDeployed(termAuction)) {
revert InvalidTermAuction(termAuction);
}

ITermAuction auction = ITermAuction(termAuction);
require(auction.termRepoId() == ITermRepoToken(repoToken).termRepoId(), "repoToken does not match term repo ID");

// Validate purchase token, min collateral ratio and insert the repoToken if necessary
repoTokenListData.validateRepoToken(
ITermRepoToken(repoToken),
termController,
address(asset)
);

_validateRepoTokenConcentration(repoToken, purchaseTokenAmount, 0);

// Prepare and submit the offer
ITermAuctionOfferLocker offerLocker = ITermAuctionOfferLocker(
auction.termAuctionOfferLocker()
);
require(
block.timestamp > offerLocker.auctionStartTime() ||
block.timestamp < auction.auctionEndTime(),
"Auction not open"
ITermAuctionOfferLocker offerLocker = _validateAndGetOfferLocker(
termAuction,
repoToken,
purchaseTokenAmount
);

// Sweep assets, redeem matured repoTokens and ensure liquid balances up to date
Expand Down Expand Up @@ -732,7 +796,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
offer.purchaseToken = address(asset);

offerIds = _submitOffer(
auction,
termAuction,
offerLocker,
offer,
repoToken,
Expand Down Expand Up @@ -823,7 +887,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
bytes32[] calldata offerIds
) external onlyManagement {
// Validate if the term auction is deployed by term
if (!termController.isTermDeployed(termAuction)) {
if (!_isTermDeployed(termAuction)) {
revert InvalidTermAuction(termAuction);
}

Expand All @@ -839,7 +903,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
// Update the term auction list data and remove completed offers
termAuctionListData.removeCompleted(
repoTokenListData,
termController,
discountRateAdapter,
address(asset)
);
Expand Down Expand Up @@ -882,15 +945,19 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
function sellRepoToken(
address repoToken,
uint256 repoTokenAmount
) external whenNotPaused nonReentrant {
) external whenNotPaused nonReentrant notBlacklisted(repoToken) {
// Ensure the amount of repoTokens to sell is greater than zero
require(repoTokenAmount > 0);

// Make sure repo token is valid and deployed by Term
if (!_isTermDeployed(repoToken)) {
revert RepoTokenList.InvalidRepoToken(address(repoToken));
}

// Validate and insert the repoToken into the list, retrieve auction rate and redemption timestamp
(uint256 discountRate, uint256 redemptionTimestamp) = repoTokenListData
.validateAndInsertRepoToken(
ITermRepoToken(repoToken),
termController,
discountRateAdapter,
address(asset)
);
Expand Down Expand Up @@ -1000,6 +1067,10 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
* to deposit in the yield source.
*/
function _deployFunds(uint256 _amount) internal override whenNotPaused {
if (depositLock) {
revert DepositPaused();
}

_sweepAsset();
_redeemRepoTokens(0);
}
Expand Down
Loading

0 comments on commit 4eef899

Please sign in to comment.