From 39cf09cd702a24d3d7c047bfe9cc2721f13de1c2 Mon Sep 17 00:00:00 2001 From: Andrew Zhou Date: Thu, 12 Sep 2024 15:17:33 -0700 Subject: [PATCH 1/2] sync rate adapter with listing --- src/TermDiscountRateAdapter.sol | 90 +++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/src/TermDiscountRateAdapter.sol b/src/TermDiscountRateAdapter.sol index ac8f5a48..f0a0d466 100644 --- a/src/TermDiscountRateAdapter.sol +++ b/src/TermDiscountRateAdapter.sol @@ -1,31 +1,21 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.18; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; import {ITermDiscountRateAdapter} from "./interfaces/term/ITermDiscountRateAdapter.sol"; import {ITermController, AuctionMetadata} from "./interfaces/term/ITermController.sol"; import {ITermRepoToken} from "./interfaces/term/ITermRepoToken.sol"; -import "@openzeppelin/contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol"; - -/** - * @title TermDiscountRateAdapter - * @notice Adapter contract to retrieve discount rates for Term repo tokens - * @dev This contract implements the ITermDiscountRateAdapter interface and interacts with the Term Controller - */ -contract TermDiscountRateAdapter is ITermDiscountRateAdapter, AccessControlUpgradeable { +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +contract TermDiscountRateAdapter is ITermDiscountRateAdapter, AccessControl { bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE"); - /// @notice The Term Controller contract + ITermController public immutable TERM_CONTROLLER; + mapping(address => mapping (bytes32 => bool)) public rateInvalid; mapping(address => uint256) public repoRedemptionHaircut; - /** - * @notice Constructor to initialize the TermDiscountRateAdapter - * @param termController_ The address of the Term Controller contract - * @param oracleWallet_ The address of the oracle wallet - */ constructor(address termController_, address oracleWallet_) { TERM_CONTROLLER = ITermController(termController_); - _grantRole(ORACLE_ROLE, oracleWallet_); + _grantRole(ORACLE_ROLE, oracleWallet_); } /** @@ -35,13 +25,60 @@ contract TermDiscountRateAdapter is ITermDiscountRateAdapter, AccessControlUpgra * @dev This function fetches the auction results for the repo token's term repo ID * and returns the clearing rate of the most recent auction */ - function getDiscountRate(address repoToken) external view returns (uint256) { + function getDiscountRate(address repoToken) public view virtual returns (uint256) { + if (repoToken == address(0)) return 0; (AuctionMetadata[] memory auctionMetadata, ) = TERM_CONTROLLER.getTermAuctionResults(ITermRepoToken(repoToken).termRepoId()); uint256 len = auctionMetadata.length; - require(len > 0); + require(len > 0, "No auctions found"); + + // If there is a re-opening auction, e.g. 2 or more results for the same token + if (len > 1) { + uint256 latestAuctionTime = auctionMetadata[len - 1].auctionClearingBlockTimestamp; + if ((block.timestamp - latestAuctionTime) < 30 minutes) { + for (int256 i = int256(len) - 2; i >= 0; i--) { + if (!rateInvalid[repoToken][auctionMetadata[uint256(i)].termAuctionId]) { + return auctionMetadata[uint256(i)].auctionClearingRate; + } + } + } else { + for (int256 i = int256(len) - 1; i >= 0; i--) { + if (!rateInvalid[repoToken][auctionMetadata[uint256(i)].termAuctionId]) { + return auctionMetadata[uint256(i)].auctionClearingRate; + } + } + } + revert("No valid auction rate found"); + } + + // If there is only 1 result (not a re-opening) then always return result + return auctionMetadata[0].auctionClearingRate; + } - return auctionMetadata[len - 1].auctionClearingRate; + /** + * @notice Sets the invalidity of the result of a specific auction for a given repo token + * @dev This function is used to mark auction results as invalid or not, typically in cases of suspected manipulation + * @param repoToken The address of the repo token associated with the auction + * @param termAuctionId The unique identifier of the term auction to be invalidated + * @param isInvalid The status of the rate invalidation + * @custom:access Restricted to accounts with the ORACLE_ROLE + */ + function setAuctionRateValidator( + address repoToken, + bytes32 termAuctionId, + bool isInvalid + ) external onlyRole(ORACLE_ROLE) { + // Fetch the auction metadata for the given repo token + (AuctionMetadata[] memory auctionMetadata, ) = TERM_CONTROLLER.getTermAuctionResults(ITermRepoToken(repoToken).termRepoId()); + + // Check if the termAuctionId exists in the metadata + bool auctionExists = _validateAuctionExistence(auctionMetadata, termAuctionId); + + // Revert if the auction doesn't exist + require(auctionExists, "Auction ID not found in metadata"); + + // Update the rate invalidation status + rateInvalid[repoToken][termAuctionId] = isInvalid; } /** @@ -52,4 +89,15 @@ contract TermDiscountRateAdapter is ITermDiscountRateAdapter, AccessControlUpgra function setRepoRedemptionHaircut(address repoToken, uint256 haircut) external onlyRole(ORACLE_ROLE) { repoRedemptionHaircut[repoToken] = haircut; } -} \ No newline at end of file + + function _validateAuctionExistence(AuctionMetadata[] memory auctionMetadata, bytes32 termAuctionId) private view returns(bool auctionExists) { + // Check if the termAuctionId exists in the metadata + bool auctionExists; + for (uint256 i = 0; i < auctionMetadata.length; i++) { + if (auctionMetadata[i].termAuctionId == termAuctionId) { + auctionExists = true; + break; + } + } + } +} From ba30a203b25c0e8cc98874afc70bf85b98585272 Mon Sep 17 00:00:00 2001 From: Andrew Zhou Date: Thu, 12 Sep 2024 15:20:01 -0700 Subject: [PATCH 2/2] remove apr oracle tests --- src/test/Oracle.t.sol | 63 ------------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 src/test/Oracle.t.sol diff --git a/src/test/Oracle.t.sol b/src/test/Oracle.t.sol deleted file mode 100644 index f7c349c4..00000000 --- a/src/test/Oracle.t.sol +++ /dev/null @@ -1,63 +0,0 @@ -pragma solidity ^0.8.18; - -import "forge-std/console2.sol"; -import {Setup} from "./utils/Setup.sol"; - -import {StrategyAprOracle} from "../periphery/StrategyAprOracle.sol"; - -contract OracleTest is Setup { - StrategyAprOracle public oracle; - - function setUp() public override { - super.setUp(); - oracle = new StrategyAprOracle(); - } - - function checkOracle(address _strategy, uint256 _delta) public { - // Check set up - // TODO: Add checks for the setup - - uint256 currentApr = oracle.aprAfterDebtChange(_strategy, 0); - - // Should be greater than 0 but likely less than 100% - assertGt(currentApr, 0, "ZERO"); - assertLt(currentApr, 1e18, "+100%"); - - // TODO: Uncomment to test the apr goes up and down based on debt changes - /** - uint256 negativeDebtChangeApr = oracle.aprAfterDebtChange(_strategy, -int256(_delta)); - - // The apr should go up if deposits go down - assertLt(currentApr, negativeDebtChangeApr, "negative change"); - - uint256 positiveDebtChangeApr = oracle.aprAfterDebtChange(_strategy, int256(_delta)); - - assertGt(currentApr, positiveDebtChangeApr, "positive change"); - */ - - // TODO: Uncomment if there are setter functions to test. - /** - vm.expectRevert("!governance"); - vm.prank(user); - oracle.setterFunction(setterVariable); - - vm.prank(management); - oracle.setterFunction(setterVariable); - - assertEq(oracle.setterVariable(), setterVariable); - */ - } - - function test_oracle(uint256 _amount, uint16 _percentChange) public { - vm.assume(_amount > minFuzzAmount && _amount < maxFuzzAmount); - _percentChange = uint16(bound(uint256(_percentChange), 10, MAX_BPS)); - - mintAndDepositIntoStrategy(strategy, user, _amount); - - uint256 _delta = (_amount * _percentChange) / MAX_BPS; - - checkOracle(address(strategy), _delta); - } - - // TODO: Deploy multiple strategies with different tokens as `asset` to test against the oracle. -}