Skip to content

Commit

Permalink
Merge branch 'runtime-fv' into refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
aazhou1 authored Sep 10, 2024
2 parents a127c5c + 3576e61 commit dc817e4
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 121 deletions.
2 changes: 1 addition & 1 deletion src/RepoTokenList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ library RepoTokenList {
(redemptionTimestamp, purchaseToken, , collateralManager) = repoToken.config();

// Validate purchase token
if (purchaseToken != address(asset)) {
if (purchaseToken != asset) {
revert InvalidRepoToken(address(repoToken));
}

Expand Down
34 changes: 0 additions & 34 deletions src/RepoTokenUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,6 @@ library RepoTokenUtils {
uint256 public constant THREESIXTY_DAYCOUNT_SECONDS = 360 days;
uint256 public constant RATE_PRECISION = 1e18;

/*//////////////////////////////////////////////////////////////
PURE FUNCTIONS
//////////////////////////////////////////////////////////////*/

/**
* @notice Convert repoToken amount to purchase token precision
* @param repoTokenPrecision The precision of the repoToken
* @param purchaseTokenPrecision The precision of the purchase token
* @param purchaseTokenAmountInRepoPrecision The amount of purchase token in repoToken precision
* @return The amount in purchase token precision
*/
function repoToPurchasePrecision(
uint256 repoTokenPrecision,
uint256 purchaseTokenPrecision,
uint256 purchaseTokenAmountInRepoPrecision
) internal pure returns (uint256) {
return (purchaseTokenAmountInRepoPrecision * purchaseTokenPrecision) / repoTokenPrecision;
}

/**
* @notice Convert purchase token amount to repoToken precision
* @param repoTokenPrecision The precision of the repoToken
* @param purchaseTokenPrecision The precision of the purchase token
* @param repoTokenAmount The amount of repoToken
* @return The amount in repoToken precision
*/
function purchaseToRepoPrecision(
uint256 repoTokenPrecision,
uint256 purchaseTokenPrecision,
uint256 repoTokenAmount
) internal pure returns (uint256) {
return (repoTokenAmount * repoTokenPrecision) / purchaseTokenPrecision;
}

/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand Down
116 changes: 35 additions & 81 deletions src/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
IERC4626 public immutable YEARN_VAULT;

/// @notice State variables
bool public depositLock;
/// @dev Previous term controller
ITermController public prevTermController;
/// @dev Current term controller
Expand All @@ -62,10 +63,9 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
TermAuctionListData internal termAuctionListData;
uint256 public timeToMaturityThreshold; // seconds
uint256 public requiredReserveRatio; // 1e18
uint256 public discountRateMarkup; // 1e18 (TODO: check this)
uint256 public discountRateMarkup; // 1e18
uint256 public repoTokenConcentrationLimit; // 1e18
mapping(address => bool) public repoTokenBlacklist;
bool public depositLock;

modifier notBlacklisted(address repoToken) {
if (repoTokenBlacklist[repoToken]) {
Expand Down Expand Up @@ -344,7 +344,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
uint256 proceeds;
if (repoToken != address(0)) {
if (!_isTermDeployed(repoToken)) {
revert RepoTokenList.InvalidRepoToken(address(repoToken));
revert RepoTokenList.InvalidRepoToken(repoToken);
}

uint256 redemptionTimestamp = repoTokenListData.validateRepoToken(
Expand Down Expand Up @@ -658,16 +658,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
(cumulativeAmount + liquidBalance);
}

/**
* @notice Deposits all available asset tokens into the liquid vault
*
* @dev This function transfers the entire balance of the asset token held by this contract
* into the associated liquid vault.
*/
function _sweepAsset() private {
YEARN_VAULT.deposit(IERC20(asset).balanceOf(address(this)), address(this));
}

/**
* @notice Checks if a term contract is marked as deployed in either the current or previous term controller
* @param termContract The address of the term contract to check
Expand Down Expand Up @@ -695,36 +685,26 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
* optimizing asset allocation.
*/
function _redeemRepoTokens(uint256 liquidAmountRequired) private {
uint256 liquidityBefore = IERC20(asset).balanceOf(address(this));

// Remove completed auction offers
termAuctionListData.removeCompleted(
repoTokenListData,
discountRateAdapter,
address(asset)
);
termAuctionListData.removeCompleted(repoTokenListData, discountRateAdapter, address(asset));

// Remove and redeem matured repoTokens
repoTokenListData.removeAndRedeemMaturedTokens();

uint256 liquidityAfter = IERC20(asset).balanceOf(address(this));
uint256 liquidityDiff = liquidityAfter - liquidityBefore;
uint256 liquidity = IERC20(asset).balanceOf(address(this));

// Deposit excess underlying balance into Yearn Vault
if (liquidityDiff > liquidAmountRequired) {
if (liquidity > liquidAmountRequired) {
unchecked {
YEARN_VAULT.deposit(
liquidityDiff - liquidAmountRequired,
address(this)
);
YEARN_VAULT.deposit(liquidity - liquidAmountRequired, address(this));
}
// Withdraw shortfall from Yearn Vault to meet required liquidity
} else if (liquidityDiff < liquidAmountRequired) {
// Withdraw shortfall from Yearn Vault to meet required liquidity
} else if (liquidity < liquidAmountRequired) {
unchecked {
_withdrawAsset(liquidAmountRequired - liquidityDiff);
_withdrawAsset(liquidAmountRequired - liquidity);
}
}
}
}

/*//////////////////////////////////////////////////////////////
STRATEGIST FUNCTIONS
Expand All @@ -749,7 +729,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
revert InvalidTermAuction(address(termAuction));
}
if (!_isTermDeployed(repoToken)) {
revert RepoTokenList.InvalidRepoToken(address(repoToken));
revert RepoTokenList.InvalidRepoToken(repoToken);
}

require(termAuction.termRepoId() == ITermRepoToken(repoToken).termRepoId(), "repoToken does not match term repo ID");
Expand Down Expand Up @@ -807,7 +787,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
);

// Sweep assets, redeem matured repoTokens and ensure liquid balances up to date
_sweepAsset();
_redeemRepoTokens(0);

bytes32 offerId = _generateOfferId(idHash, address(offerLocker));
Expand Down Expand Up @@ -836,9 +815,12 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {

// Retrieve the total liquid balance
uint256 liquidBalance = _totalLiquidBalance();
uint256 totalAssetValue = _totalAssetValue(liquidBalance);
require(totalAssetValue > 0);
uint256 liquidReserveRatio = liquidBalance * 1e18 / totalAssetValue; // NOTE: we require totalAssetValue > 0 above

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

Expand All @@ -856,7 +838,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, _totalAssetValue(liquidBalance), 0);
_validateRepoTokenConcentration(repoToken, 0, totalAssetValue, 0);
}

/**
Expand Down Expand Up @@ -931,6 +913,11 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
PendingOffer storage pendingOffer = termAuctionListData.offers[offerIds[0]];
pendingOffer.offerAmount = offer.amount;
}

if (newOfferAmount < currentOfferAmount) {
YEARN_VAULT.deposit(IERC20(asset).balanceOf(address(this)), address(this));
}

}

/**
Expand Down Expand Up @@ -964,7 +951,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
);

// Sweep any remaining assets and redeem repoTokens
_sweepAsset();
_redeemRepoTokens(0);
}

Expand All @@ -982,10 +968,9 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
}

/**
* @notice Close the auction
* @notice Required for post-processing after auction clos
*/
function auctionClosed() external {
_sweepAsset();
_redeemRepoTokens(0);
}

Expand All @@ -1007,7 +992,7 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {

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

// Validate and insert the repoToken into the list, retrieve auction rate and redemption timestamp
Expand All @@ -1019,12 +1004,13 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
);

// Sweep assets and redeem repoTokens, if needed
_sweepAsset();
_redeemRepoTokens(0);

// Retrieve total liquid balance and ensure it's greater than zero
// Retrieve total asset value and liquid balance and ensure they are greater than zero
uint256 liquidBalance = _totalLiquidBalance();
require(liquidBalance > 0);
uint256 totalAssetValue = _totalAssetValue(liquidBalance);
require(totalAssetValue > 0);

uint256 discountRate = discountRateAdapter.getDiscountRate(repoToken);

Expand Down Expand Up @@ -1060,16 +1046,16 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
}

// Ensure the remaining liquid balance is above the liquidity threshold
uint256 newLiquidBalance = liquidBalance - proceeds;
if (_liquidReserveRatio(newLiquidBalance) < requiredReserveRatio) {
uint256 newLiquidReserveRatio = ( liquidBalance - proceeds ) * 1e18 / totalAssetValue; // NOTE: we require totalAssetValue > 0 above
if (newLiquidReserveRatio < requiredReserveRatio) {
revert BalanceBelowRequiredReserveRatio();
}

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

Expand Down Expand Up @@ -1109,6 +1095,11 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
discountRateAdapter = ITermDiscountRateAdapter(_discountRateAdapter);

IERC20(_asset).safeApprove(_yearnVault, type(uint256).max);

timeToMaturityThreshold = 45 days;
requiredReserveRatio = 0.2e18;
discountRateMarkup = 0.005e18;
repoTokenConcentrationLimit = 0.1e18;
}

/*//////////////////////////////////////////////////////////////
Expand All @@ -1131,7 +1122,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
revert DepositPaused();
}

_sweepAsset();
_redeemRepoTokens(0);
}

Expand Down Expand Up @@ -1188,7 +1178,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
whenNotPaused
returns (uint256 _totalAssets)
{
_sweepAsset();
_redeemRepoTokens(0);
return _totalAssetValue(_totalLiquidBalance());
}
Expand Down Expand Up @@ -1221,38 +1210,6 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
return _totalLiquidBalance();
}

/**
* @notice Gets the max amount of `asset` that an address can deposit.
* @dev Defaults to an unlimited amount for any address. But can
* be overridden by strategists.
*
* This function will be called before any deposit or mints to enforce
* any limits desired by the strategist. This can be used for either a
* traditional deposit limit or for implementing a whitelist etc.
*
* EX:
* if(isAllowed[_owner]) return super.availableDepositLimit(_owner);
*
* This does not need to take into account any conversion rates
* from shares to assets. But should know that any non max uint256
* amounts may be converted to shares. So it is recommended to keep
* custom amounts low enough as not to cause overflow when multiplied
* by `totalSupply`.
*
* @param . The address that is depositing into the strategy.
* @return . The available amount the `_owner` can deposit in terms of `asset`
*
function availableDepositLimit(
address _owner
) public view override returns (uint256) {
TODO: If desired Implement deposit limit logic and any needed state variables .
EX:
uint256 totalAssets = TokenizedStrategy.totalAssets();
return totalAssets >= depositLimit ? 0 : depositLimit - totalAssets;
}
*/

/**
* @dev Optional function for strategist to override that can
* be called in between reports.
Expand Down Expand Up @@ -1308,12 +1265,9 @@ contract Strategy is BaseStrategy, Pausable, ReentrancyGuard {
* @param _amount The amount of asset to attempt to free.
*
function _emergencyWithdraw(uint256 _amount) internal override {
TODO: If desired implement simple logic to free deployed funds.
EX:
_amount = min(_amount, aToken.balanceOf(address(this)));
_freeFunds(_amount);
}
*/
}
7 changes: 3 additions & 4 deletions src/TermAuctionList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ library TermAuctionList {
removeNode = true;
bytes32[] memory offerIds = new bytes32[](1);
offerIds[0] = current;
offer.offerLocker.unlockOffers(offerIds); // unlocking offer in this scenario withdraws offer ammount
offer.offerLocker.unlockOffers(offerIds); // unlocking offer in this scenario withdraws offer amount
}
}

Expand All @@ -226,7 +226,6 @@ library TermAuctionList {
}

if (insertRepoToken) {
// TODO: do we need to validate termDeployed(repoToken) here?

// Auction still open => include offerAmount in totalValue
// (otherwise locked purchaseToken will be missing from TV)
Expand Down Expand Up @@ -286,7 +285,7 @@ library TermAuctionList {
// Handle new or unseen repo tokens
/// @dev offer processed, but auctionClosed not yet called and auction is new so repoToken not on List and wont be picked up
/// checking repoTokendiscountRates to make sure we are not double counting on re-openings
if (offer.termAuction.auctionCompleted() && repoTokenListData.discountRates[offer.repoToken] == 0) {
if (repoTokenListData.discountRates[offer.repoToken] == 0 && offer.termAuction.auctionCompleted()) {
if (!offer.isRepoTokenSeen) {
uint256 repoTokenAmountInBaseAssetPrecision = RepoTokenUtils.getNormalizedRepoTokenAmount(
offer.repoToken,
Expand Down Expand Up @@ -358,7 +357,7 @@ library TermAuctionList {
// Handle new repo tokens or reopening auctions
/// @dev offer processed, but auctionClosed not yet called and auction is new so repoToken not on List and wont be picked up
/// checking repoTokendiscountRates to make sure we are not double counting on re-openings
if (offer.termAuction.auctionCompleted() && repoTokenListData.discountRates[offer.repoToken] == 0) {
if (repoTokenListData.discountRates[offer.repoToken] == 0 && offer.termAuction.auctionCompleted()) {
// use normalized repoToken amount if repoToken is not in the list
if (!offer.isRepoTokenSeen) {
offerAmount = RepoTokenUtils.getNormalizedRepoTokenAmount(
Expand Down
1 change: 1 addition & 0 deletions src/TermVaultEventEmitter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ contract TermVaultEventEmitter is Initializable, UUPSUpgradeable, AccessControlU

function pairVaultContract(address vaultContract) external onlyRole(ADMIN_ROLE){
_grantRole(VAULT_CONTRACT, vaultContract);
emit VaultContractPaired(vaultContract);
}

function emitTermControllerUpdated(address oldController, address newController) external onlyRole(VAULT_CONTRACT) {
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/term/ITermVaultEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.18;

interface ITermVaultEvents {
event VaultContractPaired(address vault);

event TermControllerUpdated(address oldController, address newController);

event TimeToMaturityThresholdUpdated(uint256 oldThreshold, uint256 newThreshold);
Expand Down
2 changes: 1 addition & 1 deletion src/periphery/StrategyAprOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ contract StrategyAprOracle is AprOracleBase {
function aprAfterDebtChange(
address _strategy,
int256 _delta
) external view override returns (uint256) {
) external pure override returns (uint256) {
// TODO: Implement any necessary logic to return the most accurate
// APR estimation for the strategy.
return 1e17;
Expand Down

0 comments on commit dc817e4

Please sign in to comment.