Skip to content

Commit

Permalink
Merge pull request #90 from term-finance/runtime-fv
Browse files Browse the repository at this point in the history
Runtime fv
  • Loading branch information
aazhou1 authored Nov 9, 2024
2 parents f4c7d2f + 749626e commit 4b5321d
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 81 deletions.
10 changes: 0 additions & 10 deletions .github/workflows/deploy-sepolia-strategy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ on:
description: 'Discount rate markup'
required: false
default: '0.01'
collateralTokenAddress:
description: 'Collateral token address'
required: false
default: '0x'
minCollateralRatio:
description: 'Minimum collateral ratio'
required: false
default: '0.01'
timeToMaturityThreshold:
description: 'Time to maturity threshold'
required: false
Expand Down Expand Up @@ -70,8 +62,6 @@ jobs:
TERM_CONTROLLER_ADDRESS: ${{ vars.TERM_CONTROLLER_ADDRESS }}
DISCOUNT_RATE_ADAPTER_ADDRESS: ${{ vars.DISCOUNT_RATE_ADAPTER_ADDRESS }}
DISCOUNT_RATE_MARKUP: ${{ github.event.inputs.discountRateMarkup }}
COLLATERAL_TOKEN_ADDR: ${{ github.event.inputs.collateralTokenAddress }}
MIN_COLLATERAL_RATIO: ${{ github.event.inputs.minCollateralRatio }}
TIME_TO_MATURITY_THRESHOLD: ${{ github.event.inputs.timeToMaturityThreshold }}
REPOTOKEN_CONCENTRATION_LIMIT: ${{ github.event.inputs.repoTokenConcentrationLimit }}
ADMIN_ADDRESS: ${{ vars.ADMIN_ADDRESS }}
Expand Down
4 changes: 0 additions & 4 deletions script/Strategy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,6 @@ contract DeployStrategy is Script {
address governorRoleAddress = vm.envAddress("GOVERNOR_ROLE_ADDRESS");
address termController = vm.envAddress("TERM_CONTROLLER_ADDRESS");
uint256 discountRateMarkup = vm.envUint("DISCOUNT_RATE_MARKUP");
address[] memory collateralTokenAddr = stringToAddressArray(vm.envString("COLLATERAL_TOKEN_ADDR"));
uint256[] memory minCollateralRatio = stringToUintArray(vm.envString("MIN_COLLATERAL_RATIO"));
uint256 timeToMaturityThreshold = vm.envUint("TIME_TO_MATURITY_THRESHOLD");
uint256 repoTokenConcentrationLimit = vm.envUint("REPOTOKEN_CONCENTRATION_LIMIT");
uint256 newRequiredReserveRatio = vm.envUint("NEW_REQUIRED_RESERVE_RATIO");
Expand All @@ -184,8 +182,6 @@ contract DeployStrategy is Script {
discountRateAdapterAddress,
address(eventEmitter),
governorRoleAddress,
collateralTokenAddr,
minCollateralRatio,
termController,
repoTokenConcentrationLimit,
timeToMaturityThreshold,
Expand Down
10 changes: 2 additions & 8 deletions src/RepoTokenList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct RepoTokenListData {
//////////////////////////////////////////////////////////////*/

library RepoTokenList {
address public constant NULL_NODE = address(0);
address internal constant NULL_NODE = address(0);
uint256 internal constant INVALID_AUCTION_RATE = 0;
uint256 internal constant ZERO_AUCTION_RATE = 1; //Set to lowest nonzero number so that it is not confused with INVALID_AUCTION_RATe but still calculates as if 0.

Expand Down Expand Up @@ -193,16 +193,10 @@ library RepoTokenList {
if (listData.head == NULL_NODE) return 0;

address current = listData.head;
address tokenTermController;
while (current != NULL_NODE) {
uint256 currentMaturity = getRepoTokenMaturity(current);
uint256 repoTokenBalance = ITermRepoToken(current).balanceOf(address(this));
if (currTermController.isTermDeployed(current)){
tokenTermController = address(currTermController);
} else if (prevTermController.isTermDeployed(current)){
tokenTermController = address(prevTermController);
}
uint256 discountRate = discountRateAdapter.getDiscountRate(tokenTermController, current);
uint256 discountRate = discountRateAdapter.getDiscountRate(current);

// Convert repo token balance to base asset precision
// (ratePrecision * repoPrecision * purchasePrecision) / (repoPrecision * ratePrecision) = purchasePrecision
Expand Down
4 changes: 2 additions & 2 deletions src/RepoTokenUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {ITermRepoToken} from "./interfaces/term/ITermRepoToken.sol";
//////////////////////////////////////////////////////////////*/

library RepoTokenUtils {
uint256 public constant THREESIXTY_DAYCOUNT_SECONDS = 360 days;
uint256 public constant RATE_PRECISION = 1e18;
uint256 internal constant THREESIXTY_DAYCOUNT_SECONDS = 360 days;
uint256 internal constant RATE_PRECISION = 1e18;

/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
Expand Down
101 changes: 63 additions & 38 deletions src/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

// NOTE: To implement permissioned functions you can use the onlyManagement, onlyEmergencyAuthorized and onlyKeepers modifiers

contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
contract Strategy is BaseStrategy, Pausable, AccessControl {
using SafeERC20 for IERC20;
using RepoTokenList for RepoTokenListData;
using TermAuctionList for TermAuctionListData;
Expand All @@ -46,8 +46,6 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
* @param _discountRateAdapter The address of the discount rate adapter
* @param _eventEmitter The address of the event emitter
* @param _governorAddress The address of the governor
* @param _collateralTokens The addresses of the collateral tokens
* @param _minCollateralRatio The minimum collateral ratios
* @param _termController The address of the term controller
* @param _repoTokenConcentrationLimit The concentration limit for repoTokens
* @param _timeToMaturityThreshold The time to maturity threshold
Expand All @@ -60,15 +58,25 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
address _discountRateAdapter;
address _eventEmitter;
address _governorAddress;
address[] _collateralTokens;
uint256[] _minCollateralRatio;
address _termController;
uint256 _repoTokenConcentrationLimit;
uint256 _timeToMaturityThreshold;
uint256 _requiredReserveRatio;
uint256 _discountRateMarkup;
}

struct StrategyState {
address assetVault;
address eventEmitter;
address prevTermController;
address currTermController;
address discountRateAdapter;
uint256 timeToMaturityThreshold;
uint256 requiredReserveRatio;
uint256 discountRateMarkup;
uint256 repoTokenConcentrationLimit;
}

// Custom errors
error InvalidTermAuction(address auction);
error TimeToMaturityAboveThreshold();
Expand All @@ -77,27 +85,30 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
error RepoTokenConcentrationTooHigh(address repoToken);
error RepoTokenBlacklisted(address repoToken);
error DepositPaused();
error AuctionNotOpen();
error ZeroPurchaseTokenAmount();
error OfferNotFound();

bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");
bytes32 internal constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");

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

/// @notice State variables
bool public depositLock;
bool internal depositLock;
/// @dev Previous term controller
ITermController public prevTermController;
ITermController internal prevTermController;
/// @dev Current term controller
ITermController public currTermController;
ITermDiscountRateAdapter public discountRateAdapter;
ITermController internal currTermController;
ITermDiscountRateAdapter internal discountRateAdapter;
RepoTokenListData internal repoTokenListData;
TermAuctionListData internal termAuctionListData;
uint256 public timeToMaturityThreshold; // seconds
uint256 public requiredReserveRatio; // 1e18
uint256 public discountRateMarkup; // 1e18
uint256 public repoTokenConcentrationLimit; // 1e18
uint256 internal timeToMaturityThreshold; // seconds
uint256 internal requiredReserveRatio; // 1e18
uint256 internal discountRateMarkup; // 1e18
uint256 internal repoTokenConcentrationLimit; // 1e18
mapping(address => bool) public repoTokenBlacklist;

modifier notBlacklisted(address repoToken) {
Expand Down Expand Up @@ -157,8 +168,8 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
ITermController newTermController = ITermController(newTermControllerAddr);
address currentIteration = repoTokenListData.head;
while (currentIteration != address(0)) {
if (!currTermController.isTermDeployed(currentIteration) && !newTermController.isTermDeployed(currentIteration)) {
revert("repoToken not in controllers");
if (!_isTermDeployed(currentIteration)) {
revert RepoTokenList.InvalidRepoToken(currentIteration);
}
currentIteration = repoTokenListData.nodes[currentIteration].next;
}
Expand Down Expand Up @@ -433,8 +444,6 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
else {
simulatedLiquidityRatio = (liquidBalance - proceeds) * 10 ** 18 / assetValue;
}


}

/**
Expand Down Expand Up @@ -509,6 +518,20 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
);
}

function getStrategyState() external view returns (StrategyState memory) {
return StrategyState({
assetVault: address(YEARN_VAULT),
eventEmitter: address(TERM_VAULT_EVENT_EMITTER),
prevTermController: address(prevTermController),
currTermController: address(currTermController),
discountRateAdapter: address(discountRateAdapter),
timeToMaturityThreshold: timeToMaturityThreshold,
requiredReserveRatio: requiredReserveRatio,
discountRateMarkup: discountRateMarkup,
repoTokenConcentrationLimit: repoTokenConcentrationLimit
});
}

/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -774,7 +797,7 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
_withdrawAsset(liquidAmountRequired - liquidity);
}
}
}
}

/*//////////////////////////////////////////////////////////////
STRATEGIST FUNCTIONS
Expand Down Expand Up @@ -802,8 +825,10 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
revert RepoTokenList.InvalidRepoToken(repoToken);
}

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

if(termAuction.termRepoId() != ITermRepoToken(repoToken).termRepoId()) {
revert RepoTokenList.InvalidRepoToken(repoToken);
}

// Validate purchase token, min collateral ratio and insert the repoToken if necessary
(bool isValid, ) = repoTokenListData.validateRepoToken(
ITermRepoToken(repoToken),
Expand All @@ -818,11 +843,12 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
ITermAuctionOfferLocker offerLocker = ITermAuctionOfferLocker(
termAuction.termAuctionOfferLocker()
);
require(
block.timestamp > offerLocker.auctionStartTime() &&
block.timestamp < offerLocker.revealTime(),
"Auction not open"
);
if(
block.timestamp <= offerLocker.auctionStartTime() ||
block.timestamp >= offerLocker.revealTime()
) {
revert AuctionNotOpen();
}

return offerLocker;
}
Expand All @@ -848,12 +874,13 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
)
external
whenNotPaused
nonReentrant
notBlacklisted(repoToken)
onlyManagement
returns (bytes32[] memory offerIds)
{
require(purchaseTokenAmount > 0, "Purchase token amount must be greater than zero");
if(purchaseTokenAmount == 0) {
revert ZeroPurchaseTokenAmount();
}

ITermAuctionOfferLocker offerLocker = _validateAndGetOfferLocker(
termAuction,
Expand Down Expand Up @@ -967,7 +994,9 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
// Submit the offer and get the offer IDs
offerIds = offerLocker.lockOffers(offerSubmissions);

require(offerIds.length > 0, "No offer IDs returned");
if(offerIds.length == 0) {
revert OfferNotFound();
}

// Update the pending offers list
if (currentOfferAmount == 0) {
Expand Down Expand Up @@ -1046,7 +1075,7 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
function sellRepoToken(
address repoToken,
uint256 repoTokenAmount
) external whenNotPaused nonReentrant notBlacklisted(repoToken) {
) external whenNotPaused notBlacklisted(repoToken) {
// Ensure the amount of repoTokens to sell is greater than zero
require(repoTokenAmount > 0);

Expand All @@ -1064,7 +1093,7 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
);

if (!isRepoTokenValid) {
revert RepoTokenList.InvalidRepoToken(repoToken);
revert RepoTokenList.InvalidRepoToken(repoToken);
}

// Sweep assets and redeem repoTokens, if needed
Expand Down Expand Up @@ -1155,10 +1184,6 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard {
IERC20(_params._asset).safeApprove(_params._yearnVault, type(uint256).max);

currTermController = ITermController(_params._termController);

for (uint256 i = 0; i < _params._collateralTokens.length; i++) {
repoTokenListData.collateralTokenParams[ _params._collateralTokens[i]] = _params._minCollateralRatio[i];
}

timeToMaturityThreshold = _params._timeToMaturityThreshold;
requiredReserveRatio = _params._requiredReserveRatio;
Expand Down
8 changes: 1 addition & 7 deletions src/TermAuctionList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct TermAuctionListData {
library TermAuctionList {
using RepoTokenList for RepoTokenListData;

bytes32 public constant NULL_NODE = bytes32(0);
bytes32 internal constant NULL_NODE = bytes32(0);

/*//////////////////////////////////////////////////////////////
PRIVATE FUNCTIONS
Expand Down Expand Up @@ -278,7 +278,6 @@ library TermAuctionList {
}

uint256 offerAmount = offer.offerLocker.lockedOffer(current).amount;
address tokenTermController;

// 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
Expand All @@ -291,11 +290,6 @@ library TermAuctionList {
purchaseTokenPrecision,
discountRateAdapter.repoRedemptionHaircut(offer.repoToken)
);
if (currTermController.isTermDeployed(offer.repoToken)){
tokenTermController = address(currTermController);
} else if (prevTermController.isTermDeployed(offer.repoToken)){
tokenTermController = address(prevTermController);
}
totalValue += RepoTokenUtils.calculatePresentValue(
repoTokenAmountInBaseAssetPrecision,
purchaseTokenPrecision,
Expand Down
2 changes: 1 addition & 1 deletion src/test/TestUSDCIntegration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ contract TestUSDCIntegration is Setup {
termStrategy.setDiscountRateAdapter(address(valid));
vm.stopPrank();

assertEq(address(valid), address(termStrategy.discountRateAdapter()));
assertEq(address(valid), address(termStrategy.getStrategyState().discountRateAdapter));
}

function _getRepoTokenAmountGivenPurchaseTokenAmount(
Expand Down
14 changes: 7 additions & 7 deletions src/test/TestUSDCSellRepoToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -413,39 +413,39 @@ contract TestUSDCSellRepoToken is Setup {
vm.prank(governor);
termStrategy.setTermController(address(0));

address currentController = address(termStrategy.currTermController());
address currentController = address(termStrategy.getStrategyState().currTermController);
vm.prank(governor);
termStrategy.setTermController(address(newController));
assertEq(address(termStrategy.currTermController()), address(newController));
assertEq(address(termStrategy.prevTermController()), currentController);
assertEq(address(termStrategy.getStrategyState().currTermController), address(newController));
assertEq(address(termStrategy.getStrategyState().currTermController), currentController);

vm.expectRevert();
termStrategy.setTimeToMaturityThreshold(12345);

vm.prank(governor);
termStrategy.setTimeToMaturityThreshold(12345);
assertEq(termStrategy.timeToMaturityThreshold(), 12345);
assertEq(termStrategy.getStrategyState().timeToMaturityThreshold, 12345);

vm.expectRevert();
termStrategy.setRequiredReserveRatio(12345);

vm.prank(governor);
termStrategy.setRequiredReserveRatio(12345);
assertEq(termStrategy.requiredReserveRatio(), 12345);
assertEq(termStrategy.getStrategyState().requiredReserveRatio, 12345);

vm.expectRevert();
termStrategy.setDiscountRateMarkup(12345);

vm.prank(governor);
termStrategy.setDiscountRateMarkup(12345);
assertEq(termStrategy.discountRateMarkup(), 12345);
assertEq(termStrategy.getStrategyState().discountRateMarkup, 12345);

vm.expectRevert();
termStrategy.setCollateralTokenParams(address(mockCollateral), 12345);

vm.prank(governor);
termStrategy.setCollateralTokenParams(address(mockCollateral), 12345);
assertEq(termStrategy.discountRateMarkup(), 12345);
assertEq(termStrategy.getStrategyState().discountRateMarkup, 12345);
}

function testRepoTokenValidationFailures() public {
Expand Down
Loading

0 comments on commit 4b5321d

Please sign in to comment.