diff --git a/contracts/plugins/assets/curve/CurveAppreciatingRTokenFiatCollateral.sol b/contracts/plugins/assets/curve/CurveAppreciatingRTokenFiatCollateral.sol index a4ce5e60d2..e74f88aa4c 100644 --- a/contracts/plugins/assets/curve/CurveAppreciatingRTokenFiatCollateral.sol +++ b/contracts/plugins/assets/curve/CurveAppreciatingRTokenFiatCollateral.sol @@ -87,9 +87,16 @@ contract CurveAppreciatingRTokenFiatCollateral is CurveStableCollateral { assert(low == 0); } - // Check RToken status + // Check pool status: inner RToken must be both isReady() and + // fullyCollateralized() to prevent injection of bad debt. try pairedBasketHandler.isReady() returns (bool isReady) { - if (!isReady || low == 0 || _anyDepeggedInPool() || _anyDepeggedOutsidePool()) { + if ( + !isReady || + low == 0 || + _anyDepeggedInPool() || + _anyDepeggedOutsidePool() || + !pairedBasketHandler.fullyCollateralized() + ) { // If the price is below the default-threshold price, default eventually // uint192(+/-) is the same as Fix.plus/minus markStatus(CollateralStatus.IFFY); @@ -119,6 +126,7 @@ contract CurveAppreciatingRTokenFiatCollateral is CurveStableCollateral { } /// @dev Not up-only! The RToken can devalue its exchange rate peg + /// @dev Assumption: The RToken BU is intended to equal the reference token in value /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens function underlyingRefPerTok() public view virtual override returns (uint192) { // {ref/tok} = quantity of the reference unit token in the pool per LP token @@ -154,34 +162,13 @@ contract CurveAppreciatingRTokenFiatCollateral is CurveStableCollateral { function _anyDepeggedInPool() internal view virtual override returns (bool) { // Assumption: token0 is the RToken; token1 is the reference token - // Check RToken price against reference token, accounting for appreciation - try this.tokenPrice(0) returns (uint192 low0, uint192 high0) { - // {UoA/tok} = {UoA/tok} + {UoA/tok} - uint192 mid0 = (low0 + high0) / 2; + // Check reference token price + try this.tokenPrice(1) returns (uint192 low1, uint192 high1) { + // {target/ref} = {UoA/ref} = {UoA/ref} + {UoA/ref} + uint192 mid1 = (low1 + high1) / 2; - // Remove the appreciation portion of the RToken price - // {UoA/ref} = {UoA/tok} * {tok} / {ref} - mid0 = mid0.muluDivu(rToken.totalSupply(), rToken.basketsNeeded()); - - try this.tokenPrice(1) returns (uint192 low1, uint192 high1) { - // {target/ref} = {UoA/ref} = {UoA/ref} + {UoA/ref} - uint192 mid1 = (low1 + high1) / 2; - - // Check price of reference token - if (mid1 < pegBottom || mid1 > pegTop) return true; - - // {target/ref} = {UoA/ref} / {UoA/ref} * {target/ref} - uint192 ratio = mid0.div(mid1); // * targetPerRef(), but we know it's 1 - - // Check price of RToken relative to reference token - if (ratio < pegBottom || ratio > pegTop) return true; - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - // untested: - // pattern validated in other plugins, cost to test is high - if (errData.length == 0) revert(); // solhint-disable-line reason-string - return true; - } + // Check price of reference token + if (mid1 < pegBottom || mid1 > pegTop) return true; } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data // untested: @@ -190,6 +177,8 @@ contract CurveAppreciatingRTokenFiatCollateral is CurveStableCollateral { return true; } + // The RToken does not need to be monitored given more restrictive hard-default checks + return false; } } diff --git a/contracts/plugins/assets/curve/CurveAppreciatingRTokenSelfReferentialCollateral.sol b/contracts/plugins/assets/curve/CurveAppreciatingRTokenSelfReferentialCollateral.sol index b4f89be17b..9077355cd1 100644 --- a/contracts/plugins/assets/curve/CurveAppreciatingRTokenSelfReferentialCollateral.sol +++ b/contracts/plugins/assets/curve/CurveAppreciatingRTokenSelfReferentialCollateral.sol @@ -19,8 +19,9 @@ import "./CurveAppreciatingRTokenFiatCollateral.sol"; * tar = ETH * UoA = USD * - * @notice Curve pools with native ETH or ERC777 should be avoided, - * see docs/collateral.md for information + * @notice This Curve Pool contains WETH, which can be used to intercept execution by providing + * `use_eth=true` to remove_liquidity()/remove_liquidity_one_coin(). It is guarded against + * by the recommended method of calling `claim_admin_fees()`. */ contract CurveAppreciatingRTokenSelfReferentialCollateral is CurveAppreciatingRTokenFiatCollateral { using OracleLib for AggregatorV3Interface; @@ -36,46 +37,18 @@ contract CurveAppreciatingRTokenSelfReferentialCollateral is CurveAppreciatingRT PTConfiguration memory ptConfig ) CurveAppreciatingRTokenFiatCollateral(config, revenueHiding, ptConfig) {} - // solhint-enable no-empty-blocks + /// Should not revert (unless CurvePool is re-entrant!) + /// Refresh exchange rates and update default status. + function refresh() public virtual override { + curvePool.claim_admin_fees(); // revert if curve pool is re-entrant + super.refresh(); + } // === Internal === function _anyDepeggedInPool() internal view virtual override returns (bool) { - // Assumption: token0 is the RToken; token1 is the reference token - - // Check RToken price against reference token, accounting for appreciation - try this.tokenPrice(0) returns (uint192 low0, uint192 high0) { - // {UoA/tok} = {UoA/tok} + {UoA/tok} - uint192 mid0 = (low0 + high0) / 2; - - // Remove the appreciation portion of the RToken price - // {UoA/ref} = {UoA/tok} * {tok} / {ref} - mid0 = mid0.muluDivu(rToken.totalSupply(), rToken.basketsNeeded()); - - try this.tokenPrice(1) returns (uint192 low1, uint192 high1) { - // {UoA/ref} = {UoA/ref} + {UoA/ref} - uint192 mid1 = (low1 + high1) / 2; - - // {target/ref} = {UoA/ref} / {UoA/ref} * {target/ref} - uint192 ratio = mid0.div(mid1); // * targetPerRef(), but we know it's 1 - - // Check price of RToken relative to reference token - if (ratio < pegBottom || ratio > pegTop) return true; - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - // untested: - // pattern validated in other plugins, cost to test is high - if (errData.length == 0) revert(); // solhint-disable-line reason-string - return true; - } - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - // untested: - // pattern validated in other plugins, cost to test is high - if (errData.length == 0) revert(); // solhint-disable-line reason-string - return true; - } - + // WETH cannot de-peg against ETH (the price feed we have is ETH/USD) + // The RToken does not need to be monitored given more restrictive hard-default checks return false; } } diff --git a/contracts/plugins/assets/curve/CurveRecursiveCollateral.sol b/contracts/plugins/assets/curve/CurveRecursiveCollateral.sol index d07190c57a..c1cf7461c6 100644 --- a/contracts/plugins/assets/curve/CurveRecursiveCollateral.sol +++ b/contracts/plugins/assets/curve/CurveRecursiveCollateral.sol @@ -18,6 +18,8 @@ import "../OracleLib.sol"; * - The RToken _must_ be the same RToken using this plugin as collateral! * - The RToken SHOULD have an RSR overcollateralization layer. DO NOT USE WITHOUT RSR! * - The LP token should be worth ~2x the reference token. Do not use with 1x lpTokens. + * - Lastly: Do NOT deploy an RToken with this collateral! It can only be swapped + * in at a later date once the RToken has nonzero issuance. * * tok = ConvexStakingWrapper or CurveGaugeWrapper * ref = coins(0) in the pool @@ -30,6 +32,9 @@ contract CurveRecursiveCollateral is CurveStableCollateral { IRToken internal immutable rToken; // token1 + // does not become nonzero until after first refresh() + uint192 internal poolVirtualPrice; // {lpToken@t=0/lpToken} max virtual price sub revenue hiding + /// @param config.erc20 must be of type ConvexStakingWrapper or CurveGaugeWrapper /// @param config.chainlinkFeed Feed units: {UoA/ref} constructor( @@ -38,9 +43,6 @@ contract CurveRecursiveCollateral is CurveStableCollateral { PTConfiguration memory ptConfig ) CurveStableCollateral(config, revenueHiding, ptConfig) { rToken = IRToken(address(token1)); - - // {ref/tok} LP token's virtual price - exposedReferencePrice = _safeWrap(curvePool.get_virtual_price()).mul(revenueShowing); } /// Can revert, used by other contract functions in order to catch errors @@ -83,25 +85,42 @@ contract CurveRecursiveCollateral is CurveStableCollateral { function refresh() public virtual override { CollateralStatus oldStatus = status(); - try this.underlyingRefPerTok() returns (uint192) { + try this.underlyingRefPerTok() returns (uint192 underlyingRefPerTok_) { // Instead of ensuring the underlyingRefPerTok is up-only, solely check // that the pool's virtual price is up-only. Otherwise this collateral - // would create default cascades. + // would create default cascades when basketsNeeded()/totalSupply() falls. + + // === Check for virtualPrice hard default === - // {ref/tok} + // {lpToken@t=0/lpToken} uint192 virtualPrice = _safeWrap(curvePool.get_virtual_price()); - // {ref/tok} = {ref/tok} * {1} - uint192 hiddenReferencePrice = virtualPrice.mul(revenueShowing); + // {lpToken@t=0/lpToken} + uint192 hiddenVirtualPrice = virtualPrice.mul(revenueShowing); // uint192(<) is equivalent to Fix.lt - if (virtualPrice < exposedReferencePrice) { - exposedReferencePrice = virtualPrice; + if (virtualPrice < poolVirtualPrice) { + poolVirtualPrice = virtualPrice; markStatus(CollateralStatus.DISABLED); + } else if (hiddenVirtualPrice > poolVirtualPrice) { + poolVirtualPrice = hiddenVirtualPrice; + } + + // === Update exposedReferencePrice, ignoring default === + + // {ref/tok} = {ref/tok} * {1} + uint192 hiddenReferencePrice = underlyingRefPerTok_.mul(revenueShowing); + + // uint192(<) is equivalent to Fix.lt + if (underlyingRefPerTok_ < exposedReferencePrice) { + exposedReferencePrice = underlyingRefPerTok_; + // markStatus(CollateralStatus.DISABLED); // don't DISABLE } else if (hiddenReferencePrice > exposedReferencePrice) { exposedReferencePrice = hiddenReferencePrice; } + // === Check for soft default === + // Check for soft default + save prices try this.tryPrice() returns (uint192 low, uint192 high, uint192) { // {UoA/tok}, {UoA/tok}, {UoA/tok} diff --git a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol index 36bde1b1e3..50300f0765 100644 --- a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol @@ -89,11 +89,16 @@ contract CurveStableRTokenMetapoolCollateral is CurveStableMetapoolCollateral { assert(low == 0); } - // Check RToken status + // Check pool status: inner RToken must be both isReady() and + // fullyCollateralized() to prevent injection of bad debt. try pairedBasketHandler.isReady() returns (bool isReady) { - if (!isReady) { - markStatus(CollateralStatus.IFFY); - } else if (low == 0 || _anyDepeggedInPool() || _anyDepeggedOutsidePool()) { + if ( + !isReady || + low == 0 || + _anyDepeggedInPool() || + _anyDepeggedOutsidePool() || + !pairedBasketHandler.fullyCollateralized() + ) { // If the price is below the default-threshold price, default eventually // uint192(+/-) is the same as Fix.plus/minus markStatus(CollateralStatus.IFFY); diff --git a/contracts/plugins/assets/curve/PoolTokens.sol b/contracts/plugins/assets/curve/PoolTokens.sol index 554502d233..b99378db00 100644 --- a/contracts/plugins/assets/curve/PoolTokens.sol +++ b/contracts/plugins/assets/curve/PoolTokens.sol @@ -7,8 +7,18 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "contracts/plugins/assets/OracleLib.sol"; import "contracts/libraries/Fixed.sol"; -// solhint-disable func-name-mixedcase +// solhint-disable func-param-name-mixedcase, func-name-mixedcase interface ICurvePool { + // reentrancy check -- use with ETH / WETH pools + function claim_admin_fees() external; + + function remove_liquidity( + uint256 _amount, + uint256[2] calldata min_amounts, + bool use_eth, + address receiver + ) external; + // For Curve Plain Pools and V2 Metapools function coins(uint256) external view returns (address); diff --git a/contracts/plugins/mocks/CurvePoolMock.sol b/contracts/plugins/mocks/CurvePoolMock.sol index cd33b7ba19..4298e15a41 100644 --- a/contracts/plugins/mocks/CurvePoolMock.sol +++ b/contracts/plugins/mocks/CurvePoolMock.sol @@ -15,6 +15,15 @@ contract CurvePoolMock is ICurvePool { coins = _coins; } + function claim_admin_fees() external {} + + function remove_liquidity( + uint256 _amount, + uint256[2] calldata min_amounts, + bool use_eth, + address receiver + ) external {} + function setBalances(uint256[] memory newBalances) external { _balances = newBalances; } diff --git a/contracts/plugins/mocks/CurveReentrantReceiver.sol b/contracts/plugins/mocks/CurveReentrantReceiver.sol new file mode 100644 index 0000000000..c56ecd7288 --- /dev/null +++ b/contracts/plugins/mocks/CurveReentrantReceiver.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../../interfaces/IAsset.sol"; + +contract CurveReentrantReceiver { + ICollateral curvePlugin; + + constructor(ICollateral curvePlugin_) { + curvePlugin = curvePlugin_; + curvePlugin.refresh(); // should not revert yet + } + + fallback() external payable { + // should revert if re-entrant + try curvePlugin.refresh() {} catch { + revert("refresh() reverted"); + } + } +} diff --git a/scripts/addresses/1-tmp-assets-collateral.json b/scripts/addresses/1-tmp-assets-collateral.json index b7d3762111..e8bb4eddab 100644 --- a/scripts/addresses/1-tmp-assets-collateral.json +++ b/scripts/addresses/1-tmp-assets-collateral.json @@ -33,10 +33,10 @@ "cUSDCv3": "0x33Ba1BC07b0fafb4BBC1520B330081b91ca6bdf0", "cvx3Pool": "0x8E5ADdC553962DAcdF48106B6218AC93DA9617b2", "cvxPayPool": "0x5315Fbe0CEB299F53aE375f65fd9376767C8224c", - "cvxeUSDFRAXBP": "0xE529B59C1764d6E5a274099Eb660DD9e130A5481", + "cvxeUSDFRAXBP": "0x994455cE66Fd984e2A0A0aca453e637810a8f032", "cvxMIM3Pool": "0x3d21f841C0Fb125176C1DBDF0DE196b071323A75", - "cvxETHPlusETH": "0xc4a5Fb266E8081D605D87f0b1290F54B0a5Dc221", - "crveUSDFRAXBP": "0x945b0ad788dD6dB3864AB23876C68C1bf000d237", + "cvxETHPlusETH": "0x05F164E71C46a8f8FB2ba71550a00eeC9FCd85cd", + "crveUSDFRAXBP": "0xCDC5f5E041b49Cad373E94930E2b3bE30be70535", "crvMIM3Pool": "0x692cf8CE08d03eF1f8C3dCa82F67935fa9417B62", "crv3Pool": "0xf59a7987EDd5380cbAb30c37D1c808686f9b67B9", "sDAI": "0x62a9DDC6FF6077E823690118eCc935d16A8de47e", @@ -119,4 +119,4 @@ "CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", "CVX": "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B" } -} +} \ No newline at end of file diff --git a/scripts/addresses/mainnet-3.4.0/1-tmp-assets-collateral.json b/scripts/addresses/mainnet-3.4.0/1-tmp-assets-collateral.json index 8fec0d8289..61fc0521a8 100644 --- a/scripts/addresses/mainnet-3.4.0/1-tmp-assets-collateral.json +++ b/scripts/addresses/mainnet-3.4.0/1-tmp-assets-collateral.json @@ -33,10 +33,10 @@ "cUSDCv3": "0x33Ba1BC07b0fafb4BBC1520B330081b91ca6bdf0", "cvx3Pool": "0x8E5ADdC553962DAcdF48106B6218AC93DA9617b2", "cvxPayPool": "0x5315Fbe0CEB299F53aE375f65fd9376767C8224c", - "cvxeUSDFRAXBP": "0xE529B59C1764d6E5a274099Eb660DD9e130A5481", + "cvxeUSDFRAXBP": "0x994455cE66Fd984e2A0A0aca453e637810a8f032", "cvxMIM3Pool": "0x3d21f841C0Fb125176C1DBDF0DE196b071323A75", - "cvxETHPlusETH": "0xc4a5Fb266E8081D605D87f0b1290F54B0a5Dc221", - "crveUSDFRAXBP": "0x945b0ad788dD6dB3864AB23876C68C1bf000d237", + "cvxETHPlusETH": "0x05F164E71C46a8f8FB2ba71550a00eeC9FCd85cd", + "crveUSDFRAXBP": "0xCDC5f5E041b49Cad373E94930E2b3bE30be70535", "crvMIM3Pool": "0x692cf8CE08d03eF1f8C3dCa82F67935fa9417B62", "crv3Pool": "0xf59a7987EDd5380cbAb30c37D1c808686f9b67B9", "sDAI": "0x62a9DDC6FF6077E823690118eCc935d16A8de47e", diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 7d2f74eb00..fbb35852aa 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -64,8 +64,6 @@ import { IMPLEMENTATION, Implementation, ORACLE_ERROR, PRICE_TIMEOUT } from '../ const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip -const describeFork = useEnv('FORK') ? describe : describe.skip - const getDescribeFork = (targetNetwork = 'mainnet') => { return useEnv('FORK') && useEnv('FORK_NETWORK') === targetNetwork ? describe : describe.skip } @@ -82,6 +80,7 @@ export default function fn( isMetapool, resetFork, collateralName, + itChecksTargetPerRefDefault, itClaimsRewards, targetNetwork, } = fixtures @@ -559,78 +558,87 @@ export default function fn( expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) }) - it('enters IFFY state when reference unit depegs below low threshold', async () => { - const delayUntilDefault = await ctx.collateral.delayUntilDefault() + itChecksTargetPerRefDefault( + 'enters IFFY state when reference unit depegs below low threshold', + async () => { + const delayUntilDefault = await ctx.collateral.delayUntilDefault() - // Check initial state - expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + // Check initial state + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) - // Depeg first feed - Reducing price by 20% from 1 to 0.8 - const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('8e7')) - await updateAnswerTx.wait() + // Depeg first feed - Reducing price by 20% from 1 to 0.8 + const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('8e7')) + await updateAnswerTx.wait() - // Check status + whenDefault - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault + // Check status + whenDefault + const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 + const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - await expect(ctx.collateral.refresh()) - .to.emit(ctx.collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await ctx.collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) + await expect(ctx.collateral.refresh()) + .to.emit(ctx.collateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) + expect(await ctx.collateral.whenDefault()).to.equal(expectedDefaultTimestamp) + } + ) - it('enters IFFY state when reference unit depegs above high threshold', async () => { - const delayUntilDefault = await ctx.collateral.delayUntilDefault() + itChecksTargetPerRefDefault( + 'enters IFFY state when reference unit depegs above high threshold', + async () => { + const delayUntilDefault = await ctx.collateral.delayUntilDefault() - // Check initial state - expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + // Check initial state + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) - // Depeg first feed - Raising price by 20% from 1 to 1.2 - const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('1.2e8')) - await updateAnswerTx.wait() + // Depeg first feed - Raising price by 20% from 1 to 1.2 + const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('1.2e8')) + await updateAnswerTx.wait() - // Check status + whenDefault - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault + // Check status + whenDefault + const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 + const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - await expect(ctx.collateral.refresh()) - .to.emit(ctx.collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await ctx.collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) + await expect(ctx.collateral.refresh()) + .to.emit(ctx.collateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) + expect(await ctx.collateral.whenDefault()).to.equal(expectedDefaultTimestamp) + } + ) - it('enters DISABLED state when reference unit depegs for too long', async () => { - const delayUntilDefault = await ctx.collateral.delayUntilDefault() + itChecksTargetPerRefDefault( + 'enters DISABLED state when reference unit depegs for too long', + async () => { + const delayUntilDefault = await ctx.collateral.delayUntilDefault() - // Check initial state - expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + // Check initial state + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) - // Depeg first feed - Reducing price by 20% from 1 to 0.8 - const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('8e7')) - await updateAnswerTx.wait() + // Depeg first feed - Reducing price by 20% from 1 to 0.8 + const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('8e7')) + await updateAnswerTx.wait() - // Check status + whenDefault - await ctx.collateral.refresh() - expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) + // Check status + whenDefault + await ctx.collateral.refresh() + expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) - // Move time forward past delayUntilDefault - await advanceTime(delayUntilDefault) - expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) + // Move time forward past delayUntilDefault + await advanceTime(delayUntilDefault) + expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) - // Nothing changes if attempt to refresh after default - const prevWhenDefault: bigint = (await ctx.collateral.whenDefault()).toBigInt() - await expect(ctx.collateral.refresh()).to.not.emit( - ctx.collateral, - 'CollateralStatusChanged' - ) - expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await ctx.collateral.whenDefault()).to.equal(prevWhenDefault) - }) + // Nothing changes if attempt to refresh after default + const prevWhenDefault: bigint = (await ctx.collateral.whenDefault()).toBigInt() + await expect(ctx.collateral.refresh()).to.not.emit( + ctx.collateral, + 'CollateralStatusChanged' + ) + expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await ctx.collateral.whenDefault()).to.equal(prevWhenDefault) + } + ) it('enters DISABLED state when refPerTok() decreases', async () => { // Check initial state diff --git a/test/plugins/individual-collateral/curve/constants.ts b/test/plugins/individual-collateral/curve/constants.ts index 00131a8206..56b9519cff 100644 --- a/test/plugins/individual-collateral/curve/constants.ts +++ b/test/plugins/individual-collateral/curve/constants.ts @@ -96,6 +96,7 @@ export const ARB = networkConfig[chainId].tokens.ARB! export const ETHPLUS = networkConfig['1'].tokens.ETHPLUS! export const ETHPLUS_ASSET_REGISTRY = '0xf526f058858E4cD060cFDD775077999562b31bE0' export const ETHPLUS_BASKET_HANDLER = '0x56f40A33e3a3fE2F1614bf82CBeb35987ac10194' +export const ETHPLUS_BACKING_MANAGER = '0x608e1e01EF072c15E5Da7235ce793f4d24eCa67B' export const ETHPLUS_TIMELOCK = '0x5f4A10aE2fF68bE3cdA7d7FB432b10C6BFA6457B' // USDC+ @@ -135,6 +136,9 @@ export const eUSD_FRAX_BP = '0xAEda92e6A3B1028edc139A4ae56Ec881f3064D4F' export const eUSD_FRAX_BP_POOL_ID = 156 export const eUSD_FRAX_HOLDER = '0x8605dc0C339a2e7e85EEA043bD29d42DA2c6D784' export const eUSD_GAUGE = '0x8605dc0c339a2e7e85eea043bd29d42da2c6d784' +export const EUSD_ASSET_REGISTRY = '0x9B85aC04A09c8C813c37de9B3d563C2D3F936162' +export const EUSD_BASKET_HANDLER = '0x6d309297ddDFeA104A6E89a132e2f05ce3828e07' +export const eUSD_BACKING_MANAGER = '0xF014FEF41cCB703975827C8569a3f0940cFD80A4' // ETH+ + ETH export const ETHPLUS_BP_POOL = '0x7fb53345f1b21ab5d9510adb38f7d3590be6364b' diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts index bfb7ed9ddc..3b3e5f365f 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts @@ -218,6 +218,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it, itClaimsRewards: it, isMetapool: true, resetFork, diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index b0bb27dd07..79a7f7b030 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -291,6 +291,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it, itClaimsRewards: it, isMetapool: true, resetFork: getResetFork(forkBlockNumber['new-curve-plugins']), diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts index 22cc0d4eb6..e5a5497c28 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts @@ -227,6 +227,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it, itClaimsRewards: it, isMetapool: false, resetFork, diff --git a/test/plugins/individual-collateral/curve/cvx/CvxAppreciatingRTokenSelfReferential.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxAppreciatingRTokenSelfReferential.test.ts index fd4df26b5b..809da90715 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxAppreciatingRTokenSelfReferential.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxAppreciatingRTokenSelfReferential.test.ts @@ -42,6 +42,7 @@ import { ETHPLUS, ETHPLUS_ASSET_REGISTRY, ETHPLUS_BASKET_HANDLER, + ETHPLUS_BACKING_MANAGER, ETHPLUS_TIMELOCK, } from '../constants' import { whileImpersonating } from '../../../../utils/impersonation' @@ -261,20 +262,27 @@ const collateralSpecificStatusTests = () => { expect(await mockRTokenAsset.stale()).to.be.false }) - it('Regression test -- becomes IFFY when inner RToken is IFFY', async () => { - const [collateral] = await deployCollateral({}) + it('Regression test -- stays IFFY throughout inner RToken default + rebalancing', async () => { + const [collateral, opts] = await deployCollateral({}) const ethplusAssetRegistry = await ethers.getContractAt( 'IAssetRegistry', ETHPLUS_ASSET_REGISTRY ) + const ethplus = await ethers.getContractAt('TestIRToken', ETHPLUS) const ethplusBasketHandler = await ethers.getContractAt( - 'IBasketHandler', + 'TestIBasketHandler', ETHPLUS_BASKET_HANDLER ) const wstETHCollateral = await ethers.getContractAt( 'LidoStakedEthCollateral', await ethplusAssetRegistry.toAsset(networkConfig['1'].tokens.wstETH!) ) + const rethCollateral = await ethers.getContractAt( + 'RethCollateral', + await ethplusAssetRegistry.toAsset(networkConfig['1'].tokens.rETH!) + ) + + const initialRefPerTok = await collateral.refPerTok() const initialPrice = await wstETHCollateral.price() expect(initialPrice[0]).to.be.gt(0) expect(initialPrice[1]).to.be.lt(MAX_UINT192) @@ -285,6 +293,9 @@ const collateralSpecificStatusTests = () => { const targetPerRefOracle = await overrideOracle(targetPerRefFeed) const latestAnswer = await targetPerRefOracle.latestAnswer() await targetPerRefOracle.updateAnswer(latestAnswer.mul(4).div(5)) + const uoaPerRefFeed = await wstETHCollateral.chainlinkFeed() + const uoaPerRefOracle = await overrideOracle(uoaPerRefFeed) + await uoaPerRefOracle.updateAnswer(await uoaPerRefOracle.latestAnswer()) // wstETHCollateral + CurveAppreciatingRTokenSelfReferentialCollateral should // become IFFY through the top-level refresh @@ -310,35 +321,96 @@ const collateralSpecificStatusTests = () => { ]) expect(await wstETHCollateral.status()).to.equal(1) expect(await collateral.status()).to.equal(1) + expect(await ethplusBasketHandler.status()).to.equal(1) expect(await ethplusBasketHandler.isReady()).to.equal(false) + expect(await collateral.refPerTok()).to.equal(initialRefPerTok) // refPerTok does not fall - // Should remain IFFY for the warmupPeriod even after wstETHCollateral is SOUND - await targetPerRefOracle.updateAnswer(latestAnswer) - await expectEvents(collateral.refresh(), [ - { - contract: ethplusBasketHandler, - name: 'BasketStatusChanged', - args: [1, 0], - emitted: true, - }, - { - contract: wstETHCollateral, - name: 'CollateralStatusChanged', - args: [1, 0], - emitted: true, - }, - { - contract: collateral, - name: 'CollateralStatusChanged', - emitted: false, - }, - ]) + // Should remain IFFY while ETH+ is rebalancing + await advanceTime((await wstETHCollateral.delayUntilDefault()) + 1) // 24h + + // prevent oracles from becoming stale + for (const feedAddr of opts.feeds!) { + const feed = await ethers.getContractAt('MockV3Aggregator', feedAddr[0]) + await feed.updateAnswer(await feed.latestAnswer()) + } + const ethOracle = await overrideOracle(WETH_USD_FEED) + await ethOracle.updateAnswer(await ethOracle.latestAnswer()) + await targetPerRefOracle.updateAnswer(await targetPerRefOracle.latestAnswer()) + await uoaPerRefOracle.updateAnswer(await uoaPerRefOracle.latestAnswer()) + const rethOracle = await overrideOracle(await rethCollateral.chainlinkFeed()) + await rethOracle.updateAnswer(await rethOracle.latestAnswer()) + const rethTargetPerTokOracle = await overrideOracle( + await rethCollateral.targetPerTokChainlinkFeed() + ) + await rethTargetPerTokOracle.updateAnswer(await rethTargetPerTokOracle.latestAnswer()) + + // Should remain IFFY + expect(await ethplusBasketHandler.status()).to.equal(2) + expect(await wstETHCollateral.status()).to.equal(2) expect(await collateral.status()).to.equal(1) + await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') + await ethplusBasketHandler.refreshBasket() // swaps WETH into basket in place of wstETH + expect(await ethplusBasketHandler.status()).to.equal(0) + expect(await ethplusBasketHandler.isReady()).to.equal(false) + expect(await ethplusBasketHandler.fullyCollateralized()).to.equal(false) + expect(await collateral.refPerTok()).to.equal(initialRefPerTok) // refPerTok does not fall - // Goes back to SOUND after warmupPeriod - await advanceTime(1000) - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged').withArgs(1, 0) + // Advancing the warmupPeriod should not change anything while rebalancing + await advanceTime((await ethplusBasketHandler.warmupPeriod()) + 1) + expect(await ethplusBasketHandler.isReady()).to.equal(true) + await collateral.refresh() + expect(await ethplusBasketHandler.status()).to.equal(0) + expect(await wstETHCollateral.status()).to.equal(2) + expect(await collateral.status()).to.equal(1) + expect(await collateral.refPerTok()).to.equal(initialRefPerTok) // refPerTok does not fall + + // Should go back to SOUND after fullyCollateralized again -- backs up to WETH + const weth = await ethers.getContractAt('IERC20Metadata', networkConfig['1'].tokens.WETH!) + const whale = '0xF04a5cC80B1E94C69B48f5ee68a08CD2F09A7c3E' + const whaleBal = await weth.balanceOf(whale) + await whileImpersonating(whale, async (whaleSigner) => { + await weth.connect(whaleSigner).transfer(ETHPLUS_BACKING_MANAGER, whaleBal) + }) + expect(await ethplusBasketHandler.fullyCollateralized()).to.equal(true) + await collateral.refresh() + expect(await ethplusBasketHandler.status()).to.equal(0) expect(await collateral.status()).to.equal(0) + expect(await collateral.refPerTok()).to.equal(initialRefPerTok) // refPerTok does not fall + + // refPerTok should finally fall after a 50% haircut + const basketsNeeded = await ethplus.basketsNeeded() + await whileImpersonating(ETHPLUS_BACKING_MANAGER, async (bm) => { + console.log('whale', whaleBal, basketsNeeded, await weth.balanceOf(bm.address)) + await weth.connect(bm).transfer(whale, whaleBal.sub(basketsNeeded.mul(26).div(100))) // leave >25% WETH backing + expect(await ethplusBasketHandler.fullyCollateralized()).to.equal(false) + await ethplus.connect(bm).setBasketsNeeded(basketsNeeded.div(2)) // 50% haircut = WETH backing is sufficient + }) + expect(await ethplusBasketHandler.fullyCollateralized()).to.equal(true) + await collateral.refresh() + expect(await ethplusBasketHandler.status()).to.equal(0) + + // ETH+/ETH collateral should finally become DISABLED once refPerTok falls + expect(await collateral.status()).to.equal(2) + expect(await collateral.refPerTok()).to.be.gt(initialRefPerTok.mul(70).div(100)) + expect(await collateral.refPerTok()).to.be.lt(initialRefPerTok.mul(71).div(100)) + // 70% < refPerTok < 71%, since sqrt(0.5) = 0.707 + }) + + it('Read-only reentrancy', async () => { + const [collateral] = await deployCollateral({}) + const factory = await ethers.getContractFactory('CurveReentrantReceiver') + const reentrantReceiver = await factory.deploy(collateral.address) + + await whileImpersonating(ETHPLUS_ETH_HOLDER, async (whale) => { + const amt = bn('1e18') + const token = await ethers.getContractAt('IERC20Metadata', ETHPLUS_BP_TOKEN) + await token.connect(whale).approve(ETHPLUS_BP_POOL, amt) + const pool = await ethers.getContractAt('ICurvePool', ETHPLUS_BP_POOL) + + await expect( + pool.connect(whale).remove_liquidity(amt, [1, 1], true, reentrantReceiver.address) + ).to.be.revertedWith('refresh() reverted') + }) }) } @@ -352,6 +424,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it.skip, itClaimsRewards: it, isMetapool: false, resetFork: getResetFork(forkBlockNumber['new-curve-plugins']), diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts index fc55ecf11e..16419d9ffa 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts @@ -226,6 +226,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it, itClaimsRewards: it, isMetapool: true, resetFork, diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index cd3181a4a2..0e48f9758c 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -34,6 +34,7 @@ import { USDC_USD_FEED, USDC_ORACLE_TIMEOUT, USDC_ORACLE_ERROR, + USDT_USD_FEED, FRAX_USD_FEED, FRAX_ORACLE_TIMEOUT, FRAX_ORACLE_ERROR, @@ -42,14 +43,14 @@ import { RTOKEN_DELAY_UNTIL_DEFAULT, CurvePoolType, CRV, + EUSD_ASSET_REGISTRY, + EUSD_BASKET_HANDLER, + eUSD_BACKING_MANAGER, eUSD_FRAX_HOLDER, eUSD, } from '../constants' import { whileImpersonating } from '../../../../utils/impersonation' -const EUSD_ASSET_REGISTRY = '0x9B85aC04A09c8C813c37de9B3d563C2D3F936162' -const EUSD_BASKET_HANDLER = '0x6d309297ddDFeA104A6E89a132e2f05ce3828e07' - type Fixture = () => Promise export const defaultCvxStableCollateralOpts: CurveMetapoolCollateralOpts = { @@ -288,10 +289,10 @@ const collateralSpecificStatusTests = () => { expect(await mockRTokenAsset.stale()).to.be.false }) - it('Regression test -- becomes IFFY when inner RToken is IFFY', async () => { - const [collateral] = await deployCollateral({}) + it('Regression test -- stays IFFY throughout inner RToken default + rebalancing', async () => { + const [collateral, opts] = await deployCollateral({}) const eusdAssetRegistry = await ethers.getContractAt('IAssetRegistry', EUSD_ASSET_REGISTRY) - const eusdBasketHandler = await ethers.getContractAt('IBasketHandler', EUSD_BASKET_HANDLER) + const eusdBasketHandler = await ethers.getContractAt('TestIBasketHandler', EUSD_BASKET_HANDLER) const cUSDTCollateral = await ethers.getContractAt( 'CTokenFiatCollateral', await eusdAssetRegistry.toAsset(networkConfig['1'].tokens.cUSDT!) @@ -331,34 +332,51 @@ const collateralSpecificStatusTests = () => { ]) expect(await cUSDTCollateral.status()).to.equal(1) expect(await collateral.status()).to.equal(1) + expect(await eusdBasketHandler.status()).to.equal(1) expect(await eusdBasketHandler.isReady()).to.equal(false) - // Should remain IFFY for the warmupPeriod even after cUSDTCollateral is SOUND again - await oracle.updateAnswer(latestAnswer) - await expectEvents(collateral.refresh(), [ - { - contract: eusdBasketHandler, - name: 'BasketStatusChanged', - args: [1, 0], - emitted: true, - }, - { - contract: cUSDTCollateral, - name: 'CollateralStatusChanged', - args: [1, 0], - emitted: true, - }, - { - contract: collateral, - name: 'CollateralStatusChanged', - emitted: false, - }, - ]) + // Should remain IFFY while rebalancing + await advanceTime((await cUSDTCollateral.delayUntilDefault()) + 1) // 24h + + // prevent oracles from becoming stale + for (const feedAddr of opts.feeds!) { + const feed = await ethers.getContractAt('MockV3Aggregator', feedAddr[0]) + await feed.updateAnswer(await feed.latestAnswer()) + } + const usdcOracle = await overrideOracle(USDC_USD_FEED) + await usdcOracle.updateAnswer(await usdcOracle.latestAnswer()) + const usdtOracle = await overrideOracle(USDT_USD_FEED) + await usdtOracle.updateAnswer(await usdtOracle.latestAnswer()) + + // Should remain IFFY + expect(await eusdBasketHandler.status()).to.equal(2) + expect(await cUSDTCollateral.status()).to.equal(2) expect(await collateral.status()).to.equal(1) + await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') + await eusdBasketHandler.refreshBasket() // swaps WETH into basket in place of wstETH + expect(await eusdBasketHandler.status()).to.equal(0) + expect(await eusdBasketHandler.isReady()).to.equal(false) + expect(await eusdBasketHandler.fullyCollateralized()).to.equal(false) - // Goes back to SOUND after warmupPeriod - await advanceTime(1000) - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged').withArgs(1, 0) + // Advancing the warmupPeriod should not change anything while rebalancing + await advanceTime((await eusdBasketHandler.warmupPeriod()) + 1) + expect(await eusdBasketHandler.isReady()).to.equal(true) + await collateral.refresh() + expect(await eusdBasketHandler.status()).to.equal(0) + expect(await cUSDTCollateral.status()).to.equal(2) + expect(await collateral.status()).to.equal(1) + + // Should go back to SOUND after fullyCollateralized again -- backs up to USDC + USDT + const usdc = await ethers.getContractAt('IERC20Metadata', networkConfig['1'].tokens.USDC!) + await whileImpersonating('0x4B16c5dE96EB2117bBE5fd171E4d203624B014aa', async (whale) => { + await usdc.connect(whale).transfer(eUSD_BACKING_MANAGER, await usdc.balanceOf(whale.address)) + }) + const usdt = await ethers.getContractAt('IERC20Metadata', networkConfig['1'].tokens.USDT!) + await whileImpersonating('0xF977814e90dA44bFA03b6295A0616a897441aceC', async (whale) => { + await usdt.connect(whale).transfer(eUSD_BACKING_MANAGER, await usdt.balanceOf(whale.address)) + }) + expect(await eusdBasketHandler.fullyCollateralized()).to.equal(true) + await collateral.refresh() expect(await collateral.status()).to.equal(0) }) } @@ -373,6 +391,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it, itClaimsRewards: it, isMetapool: true, resetFork: getResetFork(forkBlockNumber['new-curve-plugins']), diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_PayPool.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_PayPool.test.ts index 2ac9d95df7..d6779fb8a1 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_PayPool.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_PayPool.test.ts @@ -210,9 +210,6 @@ const opts = { makeCollateralFixtureContext, mintCollateralTo, itChecksTargetPerRefDefault: it, - itChecksTargetPerRefDefaultUp: it, - itChecksRefPerTokDefault: it, - itHasRevenueHiding: it, itClaimsRewards: it, isMetapool: false, resetFork: getResetFork(19287000), diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDC.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDC.test.ts index bce77c0b38..51dadf4ae9 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDC.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDC.test.ts @@ -208,6 +208,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it, itClaimsRewards: it, isMetapool: false, resetFork: getResetFork(19287000), diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDT.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDT.test.ts index 7d603901ba..3d8674266b 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDT.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite_crvUSD-USDT.test.ts @@ -209,9 +209,6 @@ const opts = { makeCollateralFixtureContext, mintCollateralTo, itChecksTargetPerRefDefault: it, - itChecksTargetPerRefDefaultUp: it, - itChecksRefPerTokDefault: it, - itHasRevenueHiding: it, itClaimsRewards: it, isMetapool: false, resetFork: getResetFork(19564899), diff --git a/test/plugins/individual-collateral/curve/cvx/L2_CvxStableTestSuite_crvUSD-USDC.test.ts b/test/plugins/individual-collateral/curve/cvx/L2_CvxStableTestSuite_crvUSD-USDC.test.ts index 6c483e2c3e..69775e3bd2 100644 --- a/test/plugins/individual-collateral/curve/cvx/L2_CvxStableTestSuite_crvUSD-USDC.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/L2_CvxStableTestSuite_crvUSD-USDC.test.ts @@ -277,6 +277,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it, itClaimsRewards: it.skip, // in this file isMetapool: false, resetFork: getResetFork(FORK_BLOCK_ARBITRUM), diff --git a/test/plugins/individual-collateral/curve/cvx/L2_CvxStableTestSuite_crvUSD-USDT.test.ts b/test/plugins/individual-collateral/curve/cvx/L2_CvxStableTestSuite_crvUSD-USDT.test.ts index 03e45cff8f..3348e507fb 100644 --- a/test/plugins/individual-collateral/curve/cvx/L2_CvxStableTestSuite_crvUSD-USDT.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/L2_CvxStableTestSuite_crvUSD-USDT.test.ts @@ -277,6 +277,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it, itClaimsRewards: it.skip, // in this file isMetapool: false, resetFork: getResetFork(FORK_BLOCK_ARBITRUM), diff --git a/test/plugins/individual-collateral/curve/pluginTestTypes.ts b/test/plugins/individual-collateral/curve/pluginTestTypes.ts index 5629888b44..d9d34c215b 100644 --- a/test/plugins/individual-collateral/curve/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/curve/pluginTestTypes.ts @@ -76,6 +76,9 @@ export interface CurveCollateralTestSuiteFixtures diff --git a/test/plugins/individual-collateral/curve/stakedao/StakeDAORecursiveCollateral.test.ts b/test/plugins/individual-collateral/curve/stakedao/StakeDAORecursiveCollateral.test.ts index 81c98fab5f..e0a64670fd 100644 --- a/test/plugins/individual-collateral/curve/stakedao/StakeDAORecursiveCollateral.test.ts +++ b/test/plugins/individual-collateral/curve/stakedao/StakeDAORecursiveCollateral.test.ts @@ -302,6 +302,7 @@ const opts = { collateralSpecificStatusTests, makeCollateralFixtureContext, mintCollateralTo, + itChecksTargetPerRefDefault: it, itClaimsRewards: it.skip, // in this file isMetapool: false, resetFork: getResetFork(forkBlockNumber['new-curve-plugins']),