From bb1e494c06b0e701f91c1ea79cdfd275497c3132 Mon Sep 17 00:00:00 2001 From: aazhou1 Date: Fri, 8 Nov 2024 10:49:48 -0800 Subject: [PATCH 1/3] remove collateral token ratio initialization --- .github/workflows/deploy-sepolia-strategy.yaml | 10 ---------- script/Strategy.s.sol | 4 ---- src/Strategy.sol | 10 +--------- src/test/utils/Setup.sol | 4 ---- 4 files changed, 1 insertion(+), 27 deletions(-) diff --git a/.github/workflows/deploy-sepolia-strategy.yaml b/.github/workflows/deploy-sepolia-strategy.yaml index a1e6ea27..b4a86d9f 100644 --- a/.github/workflows/deploy-sepolia-strategy.yaml +++ b/.github/workflows/deploy-sepolia-strategy.yaml @@ -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 @@ -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 }} diff --git a/script/Strategy.s.sol b/script/Strategy.s.sol index c0d40159..3ed47b1e 100644 --- a/script/Strategy.s.sol +++ b/script/Strategy.s.sol @@ -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"); @@ -184,8 +182,6 @@ contract DeployStrategy is Script { discountRateAdapterAddress, address(eventEmitter), governorRoleAddress, - collateralTokenAddr, - minCollateralRatio, termController, repoTokenConcentrationLimit, timeToMaturityThreshold, diff --git a/src/Strategy.sol b/src/Strategy.sol index 116284e2..0c92130e 100644 --- a/src/Strategy.sol +++ b/src/Strategy.sol @@ -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 @@ -60,8 +58,6 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard { address _discountRateAdapter; address _eventEmitter; address _governorAddress; - address[] _collateralTokens; - uint256[] _minCollateralRatio; address _termController; uint256 _repoTokenConcentrationLimit; uint256 _timeToMaturityThreshold; @@ -157,7 +153,7 @@ 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)) { + if (!_isTermDeployed(currentIteration)) { revert("repoToken not in controllers"); } currentIteration = repoTokenListData.nodes[currentIteration].next; @@ -1155,10 +1151,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; diff --git a/src/test/utils/Setup.sol b/src/test/utils/Setup.sol index 47b7d5f8..7cbc35d5 100644 --- a/src/test/utils/Setup.sol +++ b/src/test/utils/Setup.sol @@ -140,16 +140,12 @@ contract Setup is ExtendedTest, IEvents { } function constructStrategy(address asset, address mockYearnVault, address discountRateAdapter, address termVaultEventEmitter, address governor, address termController) internal returns (IStrategyInterface) { - address[] memory collateralTokenAddr = new address[](0); - uint256[] memory minCollateralRatio = new uint256[](0); Strategy.StrategyParams memory params = Strategy.StrategyParams( asset, mockYearnVault, discountRateAdapter, termVaultEventEmitter, governor, - collateralTokenAddr, - minCollateralRatio, termController, 0.1e18, 45 days, From d2519dcad73171337c7c9853bdb3741e1dfdc3ed Mon Sep 17 00:00:00 2001 From: 0xddong Date: Fri, 8 Nov 2024 12:02:50 -0800 Subject: [PATCH 2/3] reducing bytecode size --- src/RepoTokenList.sol | 10 +--- src/RepoTokenUtils.sol | 4 +- src/Strategy.sol | 85 ++++++++++++++++++---------- src/TermAuctionList.sol | 8 +-- src/test/TestUSDCIntegration.t.sol | 2 +- src/test/TestUSDCSellRepoToken.t.sol | 14 ++--- 6 files changed, 69 insertions(+), 54 deletions(-) diff --git a/src/RepoTokenList.sol b/src/RepoTokenList.sol index 85ced15e..5cc35643 100644 --- a/src/RepoTokenList.sol +++ b/src/RepoTokenList.sol @@ -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. @@ -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 diff --git a/src/RepoTokenUtils.sol b/src/RepoTokenUtils.sol index 834fafb5..32c41d3c 100644 --- a/src/RepoTokenUtils.sol +++ b/src/RepoTokenUtils.sol @@ -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 diff --git a/src/Strategy.sol b/src/Strategy.sol index 0c92130e..4c341e76 100644 --- a/src/Strategy.sol +++ b/src/Strategy.sol @@ -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; @@ -65,6 +65,15 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard { uint256 _discountRateMarkup; } + struct StrategyState { + address currTermController; + address discountRateAdapter; + uint256 timeToMaturityThreshold; + uint256 requiredReserveRatio; + uint256 discountRateMarkup; + uint256 repoTokenConcentrationLimit; + } + // Custom errors error InvalidTermAuction(address auction); error TimeToMaturityAboveThreshold(); @@ -73,27 +82,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) { @@ -154,7 +166,7 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard { address currentIteration = repoTokenListData.head; while (currentIteration != address(0)) { if (!_isTermDeployed(currentIteration)) { - revert("repoToken not in controllers"); + revert RepoTokenList.InvalidRepoToken(currentIteration); } currentIteration = repoTokenListData.nodes[currentIteration].next; } @@ -429,8 +441,6 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard { else { simulatedLiquidityRatio = (liquidBalance - proceeds) * 10 ** 18 / assetValue; } - - } /** @@ -505,6 +515,17 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard { ); } + function getStrategyState() external view returns (StrategyState memory) { + return StrategyState({ + currTermController: address(currTermController), + discountRateAdapter: address(discountRateAdapter), + timeToMaturityThreshold: timeToMaturityThreshold, + requiredReserveRatio: requiredReserveRatio, + discountRateMarkup: discountRateMarkup, + repoTokenConcentrationLimit: repoTokenConcentrationLimit + }); + } + /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -770,7 +791,7 @@ contract Strategy is BaseStrategy, Pausable, AccessControl, ReentrancyGuard { _withdrawAsset(liquidAmountRequired - liquidity); } } -} + } /*////////////////////////////////////////////////////////////// STRATEGIST FUNCTIONS @@ -798,8 +819,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), @@ -814,11 +837,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; } @@ -844,12 +868,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, @@ -963,7 +988,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) { @@ -1042,7 +1069,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); @@ -1060,7 +1087,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 diff --git a/src/TermAuctionList.sol b/src/TermAuctionList.sol index bdc61e4a..917a9b4c 100644 --- a/src/TermAuctionList.sol +++ b/src/TermAuctionList.sol @@ -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 @@ -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 @@ -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, diff --git a/src/test/TestUSDCIntegration.t.sol b/src/test/TestUSDCIntegration.t.sol index 4bfa2bdf..aeab4595 100644 --- a/src/test/TestUSDCIntegration.t.sol +++ b/src/test/TestUSDCIntegration.t.sol @@ -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( diff --git a/src/test/TestUSDCSellRepoToken.t.sol b/src/test/TestUSDCSellRepoToken.t.sol index f6017d11..3e4fb4ca 100644 --- a/src/test/TestUSDCSellRepoToken.t.sol +++ b/src/test/TestUSDCSellRepoToken.t.sol @@ -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 { From f3849962eff5ead4614707f66e1cac917aa7f54e Mon Sep 17 00:00:00 2001 From: 0xddong Date: Fri, 8 Nov 2024 12:18:03 -0800 Subject: [PATCH 3/3] exposing more state variables --- src/Strategy.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Strategy.sol b/src/Strategy.sol index 4c341e76..c4b7b355 100644 --- a/src/Strategy.sol +++ b/src/Strategy.sol @@ -66,6 +66,9 @@ contract Strategy is BaseStrategy, Pausable, AccessControl { } struct StrategyState { + address assetVault; + address eventEmitter; + address prevTermController; address currTermController; address discountRateAdapter; uint256 timeToMaturityThreshold; @@ -517,6 +520,9 @@ contract Strategy is BaseStrategy, Pausable, AccessControl { 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,