diff --git a/common/configuration.ts b/common/configuration.ts index 0bd5d8ff2..71ee05a01 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -123,6 +123,7 @@ interface INetworkConfig { AAVE_V3_INCENTIVES_CONTROLLER?: string AAVE_V3_POOL?: string STARGATE_STAKING_CONTRACT?: string + CURVE_POOL_ETH_FRXETH?: string } export const networkConfig: { [key: string]: INetworkConfig } = { @@ -219,6 +220,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH + frxETH: '0xc58f3385fbc1c8ad2c0c9a061d7c13b141d7a5df' // frxETH/ETH }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', @@ -238,6 +240,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b', + CURVE_POOL_ETH_FRXETH: '0xa1F8A6807c402E4A15ef4EBa36528A3FED24E577' }, '1': { name: 'mainnet', @@ -326,6 +329,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH + frxETH: '0xc58f3385fbc1c8ad2c0c9a061d7c13b141d7a5df' // frxETH/ETH }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', @@ -342,6 +346,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b', + CURVE_POOL_ETH_FRXETH: '0xa1F8A6807c402E4A15ef4EBa36528A3FED24E577' }, '3': { name: 'tenderly', @@ -425,6 +430,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH + frxETH: '0xc58f3385fbc1c8ad2c0c9a061d7c13b141d7a5df' // frxETH/ETH }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', @@ -441,6 +447,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b', + CURVE_POOL_ETH_FRXETH: '0xa1F8A6807c402E4A15ef4EBa36528A3FED24E577' }, '5': { name: 'goerli', diff --git a/contracts/plugins/assets/FraxOracleLib.sol b/contracts/plugins/assets/FraxOracleLib.sol new file mode 100644 index 000000000..67374c8de --- /dev/null +++ b/contracts/plugins/assets/FraxOracleLib.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "../../libraries/Fixed.sol"; +import "./OracleErrors.sol"; + +interface FraxAggregatorV3Interface is AggregatorV3Interface { + function priceSource() external view returns (address); + + function addRoundData( + bool _isBadData, + uint104 _priceLow, + uint104 _priceHigh, + uint40 _timestamp + ) external; +} + +/// Used by asset plugins to price their collateral +library FraxOracleLib { + /// @dev Use for nested calls that should revert when there is a problem + /// @param timeout The number of seconds after which oracle values should be considered stale + /// @return {UoA/tok} + function price(FraxAggregatorV3Interface chainlinkFeed, uint48 timeout) + internal + view + returns (uint192) + { + try chainlinkFeed.latestRoundData() returns ( + uint80 roundId, + int256 p, + uint256, + uint256 updateTime, + uint80 answeredInRound + ) { + if (updateTime == 0 || answeredInRound < roundId) { + revert StalePrice(); + } + + // Downcast is safe: uint256(-) reverts on underflow; block.timestamp assumed < 2^48 + uint48 secondsSince = uint48(block.timestamp - updateTime); + if (secondsSince > timeout) revert StalePrice(); + + if (p == 0) revert ZeroPrice(); + + // {UoA/tok} + return shiftl_toFix(uint256(p), -int8(chainlinkFeed.decimals())); + } catch (bytes memory errData) { + // Check if the priceSource was not set: if so, the chainlink feed has been deprecated + // and a _specific_ error needs to be raised in order to avoid looking like OOG + if (errData.length == 0) { + if (chainlinkFeed.priceSource() == address(0)) { + revert StalePrice(); + } + // solhint-disable-next-line reason-string + revert(); + } + + // Otherwise, preserve the error bytes + // solhint-disable-next-line no-inline-assembly + assembly { + revert(add(32, errData), mload(errData)) + } + } + } +} diff --git a/contracts/plugins/assets/OracleErrors.sol b/contracts/plugins/assets/OracleErrors.sol new file mode 100644 index 000000000..ddb96dd9c --- /dev/null +++ b/contracts/plugins/assets/OracleErrors.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +// 0x19abf40e +error StalePrice(); +// 0x4dfba023 +error ZeroPrice(); diff --git a/contracts/plugins/assets/OracleLib.sol b/contracts/plugins/assets/OracleLib.sol index ff9bd0ef5..87db68e2b 100644 --- a/contracts/plugins/assets/OracleLib.sol +++ b/contracts/plugins/assets/OracleLib.sol @@ -3,9 +3,7 @@ pragma solidity 0.8.19; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "../../libraries/Fixed.sol"; - -error StalePrice(); -error ZeroPrice(); +import "./OracleErrors.sol"; interface EACAggregatorProxy { function aggregator() external view returns (address); diff --git a/contracts/plugins/assets/frax-eth/README.md b/contracts/plugins/assets/frax-eth/README.md index 7d32cc254..81ed35cd9 100644 --- a/contracts/plugins/assets/frax-eth/README.md +++ b/contracts/plugins/assets/frax-eth/README.md @@ -1,7 +1,5 @@ # Staked-Frax-ETH Collateral Plugin -**NOTE: The SFraxEthCollateral plugin SHOULD NOT be deployed and used until a `frxETH/ETH` chainlink oracle can be integrated with the plugin. As of 3/14/23, there is no chainlink oracle, but the FRAX team is working on getting one.** - ## Summary This plugin allows `sfrxETH` ((Staked-Frax-ETH)[https://docs.frax.finance/frax-ether/overview]) holders use their tokens as collateral in the Reserve Protocol. @@ -16,8 +14,6 @@ You can get the `frxETH/sfrxETH` exchange rate from [`sfrxETH.pricePerShare()`]( `frxETH` contract: -`wstETH` and `stETH` can be always swapped at any time to each other without any risk and limitation (Except smart contract risk), like `wETH` and `ETH`. Wrap & Unwrap app can be found here: - ## Implementation ### Units @@ -32,6 +28,9 @@ You can get the `frxETH/sfrxETH` exchange rate from [`sfrxETH.pricePerShare()`]( This function returns rate of `frxETH/sfrxETH`, getting from [pricePerShare()](https://github.com/FraxFinance/frxETH-public/blob/master/src/sfrxETH.sol#L82) function in sfrxETH contract. +#### target-per-ref price {tar/ref} + +The targetPerRef price of `ETH/frxETH` is received from the frxETH/ETH FRAX-managed oracle ([details here](https://docs.frax.finance/frax-oracle/frax-oracle-overview)). #### tryPrice This function uses `refPerTok` and the chainlink price of `USD/ETH` to return the current price range of the collateral. Once an oracle becomes available for `frxETH/ETH`, this function should be modified to use it and return the appropiate `pegPrice`. diff --git a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol index c3dbe9137..ccafd1163 100644 --- a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol +++ b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol @@ -5,13 +5,9 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "../../../libraries/Fixed.sol"; import "../AppreciatingFiatCollateral.sol"; import "../OracleLib.sol"; +import "../FraxOracleLib.sol"; import "./vendor/IsfrxEth.sol"; - -/** - * ************************************************************ - * WARNING: this plugin is not ready to be used in Production - * ************************************************************ - */ +import "./vendor/CurvePoolEmaPriceOracleWithMinMax.sol"; /** * @title SFraxEthCollateral @@ -21,20 +17,30 @@ import "./vendor/IsfrxEth.sol"; * tar = ETH * UoA = USD */ -contract SFraxEthCollateral is AppreciatingFiatCollateral { +contract SFraxEthCollateral is AppreciatingFiatCollateral, CurvePoolEmaPriceOracleWithMinMax { using OracleLib for AggregatorV3Interface; + using FraxOracleLib for FraxAggregatorV3Interface; using FixLib for uint192; - // solhint-disable no-empty-blocks - /// @param config.chainlinkFeed Feed units: {UoA/target} - constructor(CollateralConfig memory config, uint192 revenueHiding) + /// @param config.chainlinkFeed {UoA/target} price of ETH in USD terms + /// @param revenueHiding {1e18} percent amount of revenue to hide + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + address curvePoolEmaPriceOracleAddress, + uint256 _minimumCurvePoolEma, + uint256 _maximumCurvePoolEma + ) AppreciatingFiatCollateral(config, revenueHiding) + CurvePoolEmaPriceOracleWithMinMax( + curvePoolEmaPriceOracleAddress, + _minimumCurvePoolEma, + _maximumCurvePoolEma + ) { require(config.defaultThreshold > 0, "defaultThreshold zero"); } - // solhint-enable no-empty-blocks - /// Can revert, used by other contract functions in order to catch errors /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate @@ -49,22 +55,20 @@ contract SFraxEthCollateral is AppreciatingFiatCollateral { uint192 pegPrice ) { - // {UoA/tok} = {UoA/target} * {ref/tok} * {target/ref} (1) - uint192 p = chainlinkFeed.price(oracleTimeout).mul(_underlyingRefPerTok()); + // {target/ref} Get current market peg ({eth/frxeth}) + pegPrice = _safeWrap(_getCurvePoolToken1EmaPrice()); + + // {UoA/tok} = {UoA/target} * {target/ref} * {ref/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul(pegPrice).mul(_underlyingRefPerTok()); uint192 err = p.mul(oracleError, CEIL); - low = p - err; high = p + err; + low = p - err; // assert(low <= high); obviously true just by inspection - - // TODO: Currently not checking for depegs between `frxETH` and `ETH` - // Should be modified to use a `frxETH/ETH` oracle when available - pegPrice = targetPerRef(); } /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens function _underlyingRefPerTok() internal view override returns (uint192) { - uint256 rate = IsfrxEth(address(erc20)).pricePerShare(); - return _safeWrap(rate); + return _safeWrap(IsfrxEth(address(erc20)).pricePerShare()); } } diff --git a/contracts/plugins/assets/frax-eth/vendor/CurvePoolEmaPriceOracleWithMinMax.sol b/contracts/plugins/assets/frax-eth/vendor/CurvePoolEmaPriceOracleWithMinMax.sol new file mode 100644 index 000000000..75f829a0d --- /dev/null +++ b/contracts/plugins/assets/frax-eth/vendor/CurvePoolEmaPriceOracleWithMinMax.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: ISC +pragma solidity ^0.8.19; + +// Inspired by Frax Finance: https://github.com/FraxFinance + +// Original Author +// Drake Evans: https://github.com/DrakeEvans + +// Original Reviewers +// Dennis: https://github.com/denett + +// ==================================================================== + +import { ICurvePoolEmaPriceOracleWithMinMax } from "./ICurvePoolEmaPriceOracleWithMinMax.sol"; + +interface IEmaPriceOracleStableSwap { + // solhint-disable-next-line func-name-mixedcase + function price_oracle() external view returns (uint256); +} + +struct ConstructorParams { + address curvePoolEmaPriceOracleAddress; + uint256 minimumCurvePoolEma; + uint256 maximumCurvePoolEma; +} + +/// @title CurvePoolEmaPriceOracleWithMinMax +/// @author Drake Evans (Frax Finance) https://github.com/drakeevans +/// @notice An oracle for getting EMA prices from Curve +contract CurvePoolEmaPriceOracleWithMinMax is ICurvePoolEmaPriceOracleWithMinMax { + /// @notice Curve pool, source of EMA + // solhint-disable-next-line var-name-mixedcase + address public immutable CURVE_POOL_EMA_PRICE_ORACLE; + + /// @notice Precision of Curve pool price_oracle() + uint256 public constant CURVE_POOL_EMA_PRICE_ORACLE_DECIMALS = 18; + + /// @notice Maximum price of token1 in token0 units of the EMA + /// @dev Must match precision of EMA + uint256 public minimumCurvePoolEma; + + /// @notice Maximum price of token1 in token0 units of the EMA + /// @dev Must match precision of EMA + uint256 public maximumCurvePoolEma; + + constructor( + address curvePoolEmaPriceOracleAddress, + uint256 _minimumCurvePoolEma, + uint256 _maximumCurvePoolEma + ) { + CURVE_POOL_EMA_PRICE_ORACLE = curvePoolEmaPriceOracleAddress; + minimumCurvePoolEma = _minimumCurvePoolEma; + maximumCurvePoolEma = _maximumCurvePoolEma; + } + + function _getCurvePoolToken1EmaPrice() internal view returns (uint256 _token1Price) { + uint256 _priceRaw = IEmaPriceOracleStableSwap(CURVE_POOL_EMA_PRICE_ORACLE).price_oracle(); + uint256 _price = _priceRaw > maximumCurvePoolEma ? maximumCurvePoolEma : _priceRaw; + + _token1Price = _price < minimumCurvePoolEma ? minimumCurvePoolEma : _price; + } + + /// @notice The ```getCurvePoolToken1EmaPrice``` function gets the price of the second token + /// in the Curve pool (token1) + /// @dev Returned in units of the first token (token0) + /// @return _emaPrice The price of the second token in the Curve pool + function getCurvePoolToken1EmaPrice() external view returns (uint256 _emaPrice) { + return _getCurvePoolToken1EmaPrice(); + } +} diff --git a/contracts/plugins/assets/frax-eth/vendor/ICurvePoolEmaPriceOracleWithMinMax.sol b/contracts/plugins/assets/frax-eth/vendor/ICurvePoolEmaPriceOracleWithMinMax.sol new file mode 100644 index 000000000..94a8b2dbd --- /dev/null +++ b/contracts/plugins/assets/frax-eth/vendor/ICurvePoolEmaPriceOracleWithMinMax.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +interface ICurvePoolEmaPriceOracleWithMinMax { + // solhint-disable-next-line func-name-mixedcase + function CURVE_POOL_EMA_PRICE_ORACLE() external view returns (address); + + // solhint-disable-next-line func-name-mixedcase + function CURVE_POOL_EMA_PRICE_ORACLE_DECIMALS() external view returns (uint256); + + function getCurvePoolToken1EmaPrice() external view returns (uint256 _emaPrice); + + function maximumCurvePoolEma() external view returns (uint256); + + function minimumCurvePoolEma() external view returns (uint256); +} diff --git a/contracts/plugins/mocks/ChainlinkMock.sol b/contracts/plugins/mocks/ChainlinkMock.sol index 6e0fee678..17cd3b6ed 100644 --- a/contracts/plugins/mocks/ChainlinkMock.sol +++ b/contracts/plugins/mocks/ChainlinkMock.sol @@ -24,6 +24,7 @@ contract MockV3Aggregator is AggregatorV3Interface { // Additional variable to be able to test invalid behavior uint256 public latestAnsweredRound; address public aggregator; + address public priceSource; mapping(uint256 => int256) public getAnswer; mapping(uint256 => uint256) public getTimestamp; @@ -32,6 +33,7 @@ contract MockV3Aggregator is AggregatorV3Interface { constructor(uint8 _decimals, int256 _initialAnswer) { decimals = _decimals; aggregator = address(this); + priceSource = address(this); updateAnswer(_initialAnswer); } @@ -49,6 +51,17 @@ contract MockV3Aggregator is AggregatorV3Interface { latestAnsweredRound = latestRound; } + // used by Frax oracle + function addRoundData(bool isBadData, uint104 low, uint104 high, uint40 timestamp) public { + latestAnswer = int104(low + high) / 2; + latestTimestamp = block.timestamp; + latestRound++; + getAnswer[latestRound] = latestAnswer; + getTimestamp[latestRound] = block.timestamp; + getStartedAt[latestRound] = block.timestamp; + latestAnsweredRound = latestRound; + } + // Additional function to be able to test invalid Chainlink behavior function setInvalidTimestamp() public { getTimestamp[latestRound] = 0; diff --git a/contracts/plugins/mocks/EmaPriceOracleStableSwapMock.sol b/contracts/plugins/mocks/EmaPriceOracleStableSwapMock.sol new file mode 100644 index 000000000..6d94dc396 --- /dev/null +++ b/contracts/plugins/mocks/EmaPriceOracleStableSwapMock.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: ISC +pragma solidity ^0.8.19; + +interface IEmaPriceOracleStableSwap { + function price_oracle() external view returns (uint256); +} + +/// @title CurvePoolEmaPriceOracleWithMinMax +/// @author Drake Evans (Frax Finance) https://github.com/drakeevans +/// @notice An oracle for getting EMA prices from Curve +contract EmaPriceOracleStableSwapMock is IEmaPriceOracleStableSwap { + uint256 public initPrice; + uint256 internal _price; + + constructor( + uint256 _initPrice + ) { + initPrice = _initPrice; + _price = _initPrice; + } + + function resetPrice() external { + _price = initPrice; + } + + function setPrice(uint256 newPrice) external { + _price = newPrice; + } + + function price_oracle() external view returns (uint256) { + return _price; + } +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 87a53dbcf..61bac4e91 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -44,7 +44,7 @@ const config: HardhatUserConfig = { : undefined, gas: 0x1ffffffff, blockGasLimit: 0x1fffffffffffff, - allowUnlimitedContractSize: true, + allowUnlimitedContractSize: true }, localhost: { // network for long-lived mainnet forks diff --git a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts index a15ac37a2..b2628df27 100644 --- a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts @@ -213,6 +213,7 @@ export const stableOpts = { itClaimsRewards: it.skip, // untested: very complicated to get Aave to handout rewards, and none are live currently. // The StaticATokenV3LM contract is formally verified and the function we added for claimRewards() is pretty obviously correct. itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itChecksNonZeroDefaultThreshold: it, diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index e21a0f66a..8a3a07a83 100644 --- a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts @@ -286,6 +286,7 @@ const opts = { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index 8f5bb5efe..cd35ee5c0 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -243,6 +243,7 @@ const opts = { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts index fbc3f6874..a4a9c3242 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts @@ -274,6 +274,7 @@ const opts = { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index dcb238c33..f2eec97cb 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -78,6 +78,7 @@ export default function fn( getExpectedPrice, itClaimsRewards, itChecksTargetPerRefDefault, + itChecksTargetPerRefDefaultUp, itChecksRefPerTokDefault, itChecksPriceChanges, itChecksNonZeroDefaultThreshold, @@ -223,6 +224,14 @@ export default function fn( describe('prices', () => { before(resetFork) // important for getting prices/refPerToks to behave predictably + it('enters IFFY state when price becomes stale', async () => { + const oracleTimeout = await collateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(oracleTimeout / 12) + await collateral.refresh() + expect(await collateral.status()).to.equal(CollateralStatus.IFFY) + }) + itChecksPriceChanges('prices change as USD feed price changes', async () => { const oracleError = await collateral.oracleError() const expectedPrice = await getExpectedPrice(ctx) @@ -411,14 +420,6 @@ export default function fn( expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) }) - it('enters IFFY state when price becomes stale', async () => { - const oracleTimeout = await collateral.oracleTimeout() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) - await advanceBlocks(oracleTimeout / 12) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - it('decays price over priceTimeout period', async () => { await collateral.refresh() const savedLow = await collateral.savedLowPrice() @@ -453,6 +454,8 @@ export default function fn( }) describe('status', () => { + before(resetFork) + it('maintains status in normal situations', async () => { // Check initial state expect(await collateral.status()).to.equal(CollateralStatus.SOUND) @@ -489,7 +492,7 @@ export default function fn( } ) - itChecksTargetPerRefDefault( + itChecksTargetPerRefDefaultUp( 'enters IFFY state when target-per-ref depegs above high threshold', async () => { const delayUntilDefault = await collateral.delayUntilDefault() diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index 7c91bd206..0aa72a386 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -407,6 +407,7 @@ const opts = { getExpectedPrice, itClaimsRewards: it, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts index 12964d9bb..2bf70ca7c 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts @@ -219,6 +219,7 @@ const opts = { makeCollateralFixtureContext, mintCollateralTo, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itClaimsRewards: it, diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index bbabc4c8a..0ab94cd26 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -289,6 +289,7 @@ const opts = { makeCollateralFixtureContext, mintCollateralTo, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itClaimsRewards: it, diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts index 21260f990..27a7a5c21 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts @@ -228,6 +228,7 @@ const opts = { makeCollateralFixtureContext, mintCollateralTo, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itClaimsRewards: it, diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts index ce3a93e9e..e259ef05b 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts @@ -227,6 +227,7 @@ const opts = { makeCollateralFixtureContext, mintCollateralTo, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itClaimsRewards: it, diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index 9000295bb..11d8fcb5a 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -291,6 +291,7 @@ const opts = { makeCollateralFixtureContext, mintCollateralTo, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itClaimsRewards: it, diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts index 6ff50ba47..859b762b3 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts @@ -9,7 +9,6 @@ import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' import { ERC20Mock, - IERC20, MockV3Aggregator, MockV3Aggregator__factory, TestICollateral, @@ -43,7 +42,6 @@ import { CRV, THREE_POOL_HOLDER, } from '../constants' -import { whileImpersonating } from '#/test/utils/impersonation' type Fixture = () => Promise @@ -426,6 +424,7 @@ const opts = { makeCollateralFixtureContext, mintCollateralTo, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itClaimsRewards: it, diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 4d539a4a2..2919f0ebc 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -211,6 +211,7 @@ const opts = { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index ea7c5554f..180a88935 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -254,6 +254,7 @@ all.forEach((curr: FTokenEnumeration) => { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index 7ac77c01d..fb9f69b97 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -11,6 +11,9 @@ import { SfraxEthMock, TestICollateral, IsfrxEth, + SFraxEthCollateral, + EmaPriceOracleStableSwapMock__factory, + EmaPriceOracleStableSwapMock, } from '../../../../typechain' import { pushOracleForward } from '../../../utils/oracles' import { bn, fp } from '../../../../common/numbers' @@ -27,6 +30,7 @@ import { FRX_ETH, SFRX_ETH, ETH_USD_PRICE_FEED, + CURVE_POOL_EMA_PRICE_ORACLE_ADDRESS, } from './constants' import { advanceTime, @@ -42,13 +46,20 @@ import { interface SFrxEthCollateralFixtureContext extends CollateralFixtureContext { frxEth: ERC20Mock sfrxEth: IsfrxEth + curveEmaOracle: EmaPriceOracleStableSwapMock } /* Define deployment functions */ -export const defaultRethCollateralOpts: CollateralOpts = { +interface SfrxEthCollateralOpts extends CollateralOpts { + curvePoolEmaPriceOracleAddress?: string + _minimumCurvePoolEma?: BigNumberish + _maximumCurvePoolEma?: BigNumberish +} + +export const defaultRethCollateralOpts: SfrxEthCollateralOpts = { erc20: SFRX_ETH, targetName: ethers.utils.formatBytes32String('ETH'), rewardERC20: WETH, @@ -60,9 +71,14 @@ export const defaultRethCollateralOpts: CollateralOpts = { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: fp('0'), + curvePoolEmaPriceOracleAddress: CURVE_POOL_EMA_PRICE_ORACLE_ADDRESS, + _minimumCurvePoolEma: 0, + _maximumCurvePoolEma: fp(1), } -export const deployCollateral = async (opts: CollateralOpts = {}): Promise => { +export const deployCollateral = async ( + opts: SfrxEthCollateralOpts = {} +): Promise => { opts = { ...defaultRethCollateralOpts, ...opts } const SFraxEthCollateralFactory: ContractFactory = await ethers.getContractFactory( @@ -82,13 +98,15 @@ export const deployCollateral = async (opts: CollateralOpts = {}): Promise( + await ethers.getContractFactory('EmaPriceOracleStableSwapMock') + ) + + const curveEmaOracle = ( + await EmaPriceOracleStableSwapMockFactory.deploy(fp('0.997646')) + ) + collateralOpts.curvePoolEmaPriceOracleAddress = curveEmaOracle.address + const frxEth = (await ethers.getContractAt('ERC20Mock', FRX_ETH)) as ERC20Mock const sfrxEth = (await ethers.getContractAt('IsfrxEth', SFRX_ETH)) as IsfrxEth const collateral = await deployCollateral(collateralOpts) @@ -126,6 +153,7 @@ const makeCollateralFixtureContext = ( chainlinkFeed, frxEth, sfrxEth, + curveEmaOracle, tok: sfrxEth, } } @@ -146,11 +174,27 @@ const mintCollateralTo: MintCollateralFunc = as await mintSfrxETH(ctx.sfrxEth, user, amount, recipient, ctx.chainlinkFeed) } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} +const changeTargetPerRef = async ( + ctx: SFrxEthCollateralFixtureContext, + percentChange: BigNumber +) => { + const initPrice = await ctx.curveEmaOracle.price_oracle() + await ctx.curveEmaOracle.setPrice(initPrice.add(initPrice.mul(percentChange).div(100))) +} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} +const reduceTargetPerRef = async ( + ctx: SFrxEthCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + await changeTargetPerRef(ctx, bn(pctDecrease).mul(-1)) +} + +const increaseTargetPerRef = async ( + ctx: SFrxEthCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + await changeTargetPerRef(ctx, bn(pctIncrease)) +} // prettier-ignore const reduceRefPerTok = async () => { @@ -172,11 +216,11 @@ const increaseRefPerTok = async ( await hre.network.provider.send('evm_mine', []) } await ctx.sfrxEth.syncRewards() - await advanceBlocks(1200 / 12) - await advanceTime(1200) + await advanceBlocks(86400 / 12) + await advanceTime(86400) // push chainlink oracle forward so that tryPrice() still works - const lastAnswer = await ctx.chainlinkFeed.latestAnswer() - await ctx.chainlinkFeed.updateAnswer(lastAnswer) + const latestRoundData = await ctx.chainlinkFeed.latestRoundData() + await ctx.chainlinkFeed.updateAnswer(latestRoundData.answer) } const getExpectedPrice = async (ctx: SFrxEthCollateralFixtureContext): Promise => { @@ -184,9 +228,18 @@ const getExpectedPrice = async (ctx: SFrxEthCollateralFixtureContext): Promise { const chainlinkFeed = ( await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, chainlinkDefaultAnswer) ) + const collateral = await deployCollateral({ erc20: erc20.address, revenueHiding: fp('0.01'), @@ -256,14 +310,16 @@ const opts = { increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it.skip, itChecksRefPerTokDefault: it.skip, itChecksPriceChanges: it, - itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it.skip, // implemnted in this file + itChecksNonZeroDefaultThreshold: it, resetFork, collateralName: 'SFraxEthCollateral', chainlinkDefaultAnswer, + itIsPricedByPeg: true, } collateralTests(opts) diff --git a/test/plugins/individual-collateral/frax-eth/constants.ts b/test/plugins/individual-collateral/frax-eth/constants.ts index 8ae4cfc38..aa6cda39c 100644 --- a/test/plugins/individual-collateral/frax-eth/constants.ts +++ b/test/plugins/individual-collateral/frax-eth/constants.ts @@ -7,6 +7,9 @@ export const FRX_ETH = networkConfig['31337'].tokens.frxETH as string export const SFRX_ETH = networkConfig['31337'].tokens.sfrxETH as string export const WETH = networkConfig['31337'].tokens.WETH as string export const FRX_ETH_MINTER = '0xbAFA44EFE7901E04E39Dad13167D089C559c1138' +export const FRXETH_ETH_PRICE_FEED = networkConfig['31337'].chainlinkFeeds.frxETH as string +export const CURVE_POOL_EMA_PRICE_ORACLE_ADDRESS = networkConfig['31337'] + .CURVE_POOL_ETH_FRXETH as string export const PRICE_TIMEOUT = bn('604800') // 1 week export const ORACLE_TIMEOUT = bn(86400) // 24 hours in seconds @@ -15,4 +18,4 @@ export const DEFAULT_THRESHOLD = bn(5).mul(bn(10).pow(16)) // 0.05 export const DELAY_UNTIL_DEFAULT = bn(86400) export const MAX_TRADE_VOL = bn(1000) -export const FORK_BLOCK = 16773193 +export const FORK_BLOCK = 18705637 diff --git a/test/plugins/individual-collateral/frax-eth/helpers.ts b/test/plugins/individual-collateral/frax-eth/helpers.ts index 50a24bb9b..99ce493da 100644 --- a/test/plugins/individual-collateral/frax-eth/helpers.ts +++ b/test/plugins/individual-collateral/frax-eth/helpers.ts @@ -5,6 +5,8 @@ import { BigNumberish } from 'ethers' import { FORK_BLOCK, FRX_ETH_MINTER } from './constants' import { getResetFork } from '../helpers' import { setNextBlockTimestamp, getLatestBlockTimestamp } from '../../../utils/time' +import { fp } from '#/common/numbers' +import { setBalance } from '@nomicfoundation/hardhat-network-helpers' export const mintSfrxETH = async ( sfrxEth: IsfrxEth, @@ -13,6 +15,7 @@ export const mintSfrxETH = async ( recipient: string, chainlinkFeed: MockV3Aggregator ) => { + await setBalance(account.address, fp(100000)) const frxEthMinter: IfrxEthMinter = ( await ethers.getContractAt('IfrxEthMinter', FRX_ETH_MINTER) ) @@ -28,8 +31,8 @@ export const mintSfrxETH = async ( await frxEthMinter.connect(account).submitAndDeposit(recipient, { value: depositAmount }) // push chainlink oracle forward so that tryPrice() still works - const lastAnswer = await chainlinkFeed.latestAnswer() - await chainlinkFeed.updateAnswer(lastAnswer) + const lastAnswer = await chainlinkFeed.latestRoundData() + await chainlinkFeed.updateAnswer(lastAnswer.answer) } export const mintFrxETH = async ( diff --git a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts index 43d09c95b..28d0f1bcd 100644 --- a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts @@ -193,6 +193,7 @@ const opts = { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksNonZeroDefaultThreshold: it.skip, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, diff --git a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts index 1f4213ac6..366c8c81c 100644 --- a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts @@ -265,6 +265,7 @@ const opts = { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index a573f4888..afb9e7da0 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -360,6 +360,7 @@ const makeAaveFiatCollateralTestSuite = ( getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index f9a0339f9..5428212d2 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -233,6 +233,7 @@ const makeAaveNonFiatCollateralTestSuite = ( getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts index 8e934cedc..6c1b80f2c 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -227,6 +227,7 @@ const opts = { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefaultUp: it.skip, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it.skip, diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index 34bfefbb2..2dca2653d 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -91,6 +91,9 @@ export interface CollateralTestSuiteFixtures // toggle on or off: tests that focus on a targetPerRef default itChecksTargetPerRefDefault: Mocha.TestFunction | Mocha.PendingTestFunction + // toggle on or off: tests that focus on a targetPerRef defaulting upwards + itChecksTargetPerRefDefaultUp: Mocha.TestFunction | Mocha.PendingTestFunction + // toggle on or off: tests that focus on a refPerTok default itChecksRefPerTokDefault: Mocha.TestFunction | Mocha.PendingTestFunction diff --git a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts index d10488770..f766a3bc0 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -272,6 +272,7 @@ const opts = { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itChecksNonZeroDefaultThreshold: it, diff --git a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts index f3f81e978..1968edfe8 100644 --- a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts +++ b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts @@ -312,6 +312,7 @@ export const stableOpts = { increaseTargetPerRef, itClaimsRewards: it, // reward growth not supported in mock itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, diff --git a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts index 9242e0fb0..835b90db4 100644 --- a/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/yearnv2/YearnV2CurveFiatCollateral.test.ts @@ -237,6 +237,7 @@ tests.forEach((test: CurveFiatTest) => { makeCollateralFixtureContext, mintCollateralTo, itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, itChecksRefPerTokDefault: it, itHasRevenueHiding: it, itClaimsRewards: it.skip, diff --git a/test/utils/oracles.ts b/test/utils/oracles.ts index 2444878fe..6dde6dec0 100644 --- a/test/utils/oracles.ts +++ b/test/utils/oracles.ts @@ -5,6 +5,8 @@ import { ethers, network } from 'hardhat' import { expect } from 'chai' import { fp, bn, divCeil } from '../../common/numbers' import { MAX_UINT192 } from '../../common/constants' +import { getLatestBlockTimestamp } from './time' +import { whileImpersonating } from './impersonation' const toleranceDivisor = bn('1e15') // 1 part in 1000 trillions @@ -143,7 +145,12 @@ export const overrideOracle = async (oracleAddress: string): Promise { const chainlinkFeed = await ethers.getContractAt('MockV3Aggregator', await chainlinkAddr) - const initPrice = await chainlinkFeed.latestAnswer() + let initPrice + // awkward workaround for sfrxETH oracle + try { + initPrice = await chainlinkFeed.latestAnswer() + } catch { + initPrice = (await chainlinkFeed.latestRoundData()).answer + } try { // Try to update as if it's a mock already await chainlinkFeed.updateAnswer(initPrice) @@ -163,3 +176,13 @@ export const pushOracleForward = async (chainlinkAddr: string) => { await oracle.updateAnswer(initPrice) } } + +export const pushFraxOracleForward = async (chainlinkAddr: string) => { + const chainlinkFeed = await ethers.getContractAt('FraxAggregatorV3Interface', chainlinkAddr) + const initPrice = (await chainlinkFeed.latestRoundData()).answer + await whileImpersonating(await chainlinkFeed.priceSource(), async (owner) => { + await chainlinkFeed + .connect(owner) + .addRoundData(false, initPrice, initPrice, (await getLatestBlockTimestamp()) + 1) + }) +}