diff --git a/contracts/plugins/assets/curve/CurveAppreciatingRTokenFiatCollateral.sol b/contracts/plugins/assets/curve/CurveAppreciatingRTokenFiatCollateral.sol index 887371ee0c..7dd774ccbb 100644 --- a/contracts/plugins/assets/curve/CurveAppreciatingRTokenFiatCollateral.sol +++ b/contracts/plugins/assets/curve/CurveAppreciatingRTokenFiatCollateral.sol @@ -47,10 +47,9 @@ contract CurveAppreciatingRTokenFiatCollateral is CurveStableCollateral { /// Refresh exchange rates and update default status. /// Have to override to add custom default checks function refresh() public virtual override { - // solhint-disable-next-line no-empty-blocks - try pairedAssetRegistry.refresh() {} catch { - // must allow failure since cannot brick refresh() - } + // Note: Intentionally not refreshing AssetRegistry as an acceptable trade-off + // Issuance throttle becomes worst-case outcome in case of issuance frontruns in + // response to picking up bad collateral CollateralStatus oldStatus = status(); diff --git a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol index 63c5024b52..a4be74d1b0 100644 --- a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol @@ -49,10 +49,9 @@ contract CurveStableRTokenMetapoolCollateral is CurveStableMetapoolCollateral { /// Refresh exchange rates and update default status. /// Have to override to add custom default checks function refresh() public virtual override { - // solhint-disable-next-line no-empty-blocks - try pairedAssetRegistry.refresh() {} catch { - // must allow failure since cannot brick refresh() - } + // Note: Intentionally not refreshing AssetRegistry as an acceptable trade-off + // Issuance throttle becomes worst-case outcome in case of issuance frontruns in + // response to picking up bad collateral CollateralStatus oldStatus = status(); diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index 79a7f7b030..820192dd9d 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -245,7 +245,7 @@ const collateralSpecificStatusTests = () => { await collateral.refresh() }) - it('Regression test -- refreshes inner RTokenAsset on refresh()', async () => { + it('Regression test -- does not refresh inner RTokenAsset on refresh()', async () => { const [collateral] = await deployCollateral({}) const initialPrice = await collateral.price() expect(initialPrice[0]).to.be.gt(0) @@ -276,8 +276,8 @@ const collateralSpecificStatusTests = () => { // Refresh CurveStableRTokenMetapoolCollateral await collateral.refresh() - // Stale should be false again - expect(await mockRTokenAsset.stale()).to.be.false + // Stale remains true + expect(await mockRTokenAsset.stale()).to.be.true }) } diff --git a/test/plugins/individual-collateral/curve/cvx/CvxAppreciatingRTokenSelfReferential.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxAppreciatingRTokenSelfReferential.test.ts index 809da90715..376825bc97 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxAppreciatingRTokenSelfReferential.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxAppreciatingRTokenSelfReferential.test.ts @@ -227,7 +227,7 @@ const collateralSpecificStatusTests = () => { await collateral.refresh() }) - it('Regression test -- refreshes inner RTokenAsset on refresh()', async () => { + it('Regression test -- does not refresh inner RTokenAsset on refresh()', async () => { const [collateral] = await deployCollateral({}) const initialPrice = await collateral.price() expect(initialPrice[0]).to.be.gt(0) @@ -258,8 +258,8 @@ const collateralSpecificStatusTests = () => { // Refresh CurveAppreciatingRTokenSelfReferentialCollateral await collateral.refresh() - // Stale should be false again - expect(await mockRTokenAsset.stale()).to.be.false + // Stale remains true + expect(await mockRTokenAsset.stale()).to.be.true }) it('Regression test -- stays IFFY throughout inner RToken default + rebalancing', async () => { @@ -297,9 +297,28 @@ const collateralSpecificStatusTests = () => { const uoaPerRefOracle = await overrideOracle(uoaPerRefFeed) await uoaPerRefOracle.updateAnswer(await uoaPerRefOracle.latestAnswer()) - // wstETHCollateral + CurveAppreciatingRTokenSelfReferentialCollateral should - // become IFFY through the top-level refresh + // wstETHCollateral + CurveAppreciatingRTokenSelfReferentialCollateral will NOT + // become IFFY through the top-level refresh (optimization) await expectEvents(collateral.refresh(), [ + { + contract: ethplusBasketHandler, + name: 'BasketStatusChanged', + emitted: false, + }, + { + contract: wstETHCollateral, + name: 'CollateralStatusChanged', + emitted: false, + }, + { + contract: collateral, + name: 'CollateralStatusChanged', + emitted: false, + }, + ]) + + // Refresh inner RToken to set IFFY + await expectEvents(ethplusAssetRegistry.refresh(), [ { contract: ethplusBasketHandler, name: 'BasketStatusChanged', @@ -312,6 +331,11 @@ const collateralSpecificStatusTests = () => { args: [0, 1], emitted: true, }, + ]) + + // Now we can refresh CurveAppreciatingRTokenSelfReferentialCollateral + // and it should become IFFY + await expectEvents(collateral.refresh(), [ { contract: collateral, name: 'CollateralStatusChanged', @@ -319,6 +343,7 @@ const collateralSpecificStatusTests = () => { emitted: true, }, ]) + expect(await wstETHCollateral.status()).to.equal(1) expect(await collateral.status()).to.equal(1) expect(await ethplusBasketHandler.status()).to.equal(1) @@ -380,7 +405,6 @@ const collateralSpecificStatusTests = () => { // 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 diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index 0e48f9758c..ae8cc63c67 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -254,7 +254,7 @@ const collateralSpecificStatusTests = () => { await collateral.refresh() }) - it('Regression test -- refreshes inner RTokenAsset on refresh()', async () => { + it('Regression test -- does not refresh inner RTokenAsset on refresh()', async () => { const [collateral] = await deployCollateral({}) const initialPrice = await collateral.price() expect(initialPrice[0]).to.be.gt(0) @@ -285,8 +285,8 @@ const collateralSpecificStatusTests = () => { // Refresh CurveStableRTokenMetapoolCollateral await collateral.refresh() - // Stale should be false again - expect(await mockRTokenAsset.stale()).to.be.false + // Stale remains true + expect(await mockRTokenAsset.stale()).to.be.true }) it('Regression test -- stays IFFY throughout inner RToken default + rebalancing', async () => { @@ -308,9 +308,28 @@ const collateralSpecificStatusTests = () => { const latestAnswer = await oracle.latestAnswer() await oracle.updateAnswer(latestAnswer.mul(4).div(5)) - // CTokenFiatCollateral + CurveStableRTokenMetapoolCollateral should - // become IFFY through the top-level refresh + // CTokenFiatCollateral + CurveStableRTokenMetapoolCollateral will NOT + // become IFFY through the top-level refresh (optimization) await expectEvents(collateral.refresh(), [ + { + contract: eusdBasketHandler, + name: 'BasketStatusChanged', + emitted: false, + }, + { + contract: cUSDTCollateral, + name: 'CollateralStatusChanged', + emitted: false, + }, + { + contract: collateral, + name: 'CollateralStatusChanged', + emitted: false, + }, + ]) + + // Refresh inner RToken to set IFFY + await expectEvents(eusdAssetRegistry.refresh(), [ { contract: eusdBasketHandler, name: 'BasketStatusChanged', @@ -323,6 +342,11 @@ const collateralSpecificStatusTests = () => { args: [0, 1], emitted: true, }, + ]) + + // Now we can refresh CurveStableRTokenMetapoolCollateral + // and it should become IFFY + await expectEvents(collateral.refresh(), [ { contract: collateral, name: 'CollateralStatusChanged', @@ -330,6 +354,7 @@ const collateralSpecificStatusTests = () => { emitted: true, }, ]) + expect(await cUSDTCollateral.status()).to.equal(1) expect(await collateral.status()).to.equal(1) expect(await eusdBasketHandler.status()).to.equal(1)