Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runtime fv #90

Merged
merged 5 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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